using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; using System.Text.RegularExpressions; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Microsoft.Win32; using System.Threading; using System.Threading.Tasks; namespace VRCX { public partial class AppApiElectron { public static string _homeDirectory; public static string _steamPath; public static string _steamUserdataPath; public static string _vrcPrefixPath; public static string _vrcAppDataPath; public static string _vrcCrashesPath; static AppApiElectron() { const string vrchatAppid = "438100"; _homeDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); _steamUserdataPath = Path.Join(_homeDirectory, ".steam/steam/userdata"); _steamPath = Path.Join(_homeDirectory, ".local/share/Steam"); var flatpakSteamPath = Path.Join(_homeDirectory, ".var/app/com.valvesoftware.Steam/.local/share/Steam"); if (!IsValidSteamPath(_steamPath) && IsValidSteamPath(flatpakSteamPath)) { logger.Info("Flatpak Steam detected."); _steamPath = flatpakSteamPath; } var legacySteamPath = Path.Join(_homeDirectory, ".steam/steam"); if (!IsValidSteamPath(_steamPath) && IsValidSteamPath(legacySteamPath)) { logger.Info("Legacy Steam path detected."); _steamPath = legacySteamPath; } if (!IsValidSteamPath(_steamPath)) { logger.Error("No valid Steam library found."); } var libraryFoldersVdfPath = Path.Join(_steamPath, "config/libraryfolders.vdf"); var vrcLibraryPath = GetLibraryWithAppId(libraryFoldersVdfPath, vrchatAppid); if (string.IsNullOrEmpty(vrcLibraryPath)) { logger.Warn("Falling back to default VRChat path as libraryfolders.vdf was not found OR libraryfolders.vdf does not contain VRChat's appid (438100)"); vrcLibraryPath = _steamPath; } logger.Info($"Using steam library path {vrcLibraryPath}"); _vrcPrefixPath = Path.Join(vrcLibraryPath, $"steamapps/compatdata/{vrchatAppid}/pfx"); _vrcAppDataPath = Path.Join(_vrcPrefixPath, "drive_c/users/steamuser/AppData/LocalLow/VRChat/VRChat"); _vrcCrashesPath = Path.Join(_vrcPrefixPath, "drive_c/users/steamuser/AppData/Local/Temp/VRChat/VRChat/Crashes"); } private static bool IsValidSteamPath(string path) { return File.Exists(Path.Join(path, "config/libraryfolders.vdf")); } private static string? GetLibraryWithAppId(string libraryFoldersVdfPath, string appId) { if (!File.Exists(libraryFoldersVdfPath)) return null; string? libraryPath = null; foreach (var line in File.ReadLines(libraryFoldersVdfPath)) { // Assumes line will be \t\t"path"\t\t"pathToLibrary" if (line.Contains("\"path\"")) { var parts = line.Split("\t"); if (parts.Length < 4) continue; libraryPath = parts[4].Replace("\"", ""); } if (line.Contains($"\"{appId}\"") && Directory.Exists(libraryPath)) return libraryPath; } return null; } public override string GetVRChatAppDataLocation() { return _vrcAppDataPath; } public override string GetVRChatCacheLocation() { var defaultPath = Path.Join(GetVRChatAppDataLocation(), "Cache-WindowsPlayer"); try { var json = ReadConfigFile(); if (string.IsNullOrEmpty(json)) return defaultPath; var obj = JsonConvert.DeserializeObject(json, JsonSerializerSettings); if (obj["cache_directory"] == null) return defaultPath; var cacheDir = (string)obj["cache_directory"]; if (string.IsNullOrEmpty(cacheDir)) return defaultPath; var cachePath = Path.Join(cacheDir, "Cache-WindowsPlayer"); if (!Directory.Exists(cacheDir)) return defaultPath; return cachePath; } catch (Exception e) { logger.Error($"Error reading VRChat config file for cache location: {e}"); } return defaultPath; } public override string GetVRChatPhotosLocation() { var defaultPath = Path.Join(_vrcPrefixPath, "drive_c/users/steamuser/Pictures/VRChat"); try { var json = ReadConfigFile(); if (string.IsNullOrEmpty(json)) return defaultPath; var obj = JsonConvert.DeserializeObject(json, JsonSerializerSettings); if (obj["picture_output_folder"] == null) return defaultPath; var photosDir = (string)obj["picture_output_folder"]; if (string.IsNullOrEmpty(photosDir) || !Directory.Exists(photosDir)) return defaultPath; return photosDir; } catch (Exception e) { logger.Error($"Error reading VRChat config file for photos location: {e}"); } return defaultPath; } public override string GetUGCPhotoLocation(string path = "") { if (string.IsNullOrEmpty(path)) { return GetVRChatPhotosLocation(); } try { if (!Directory.Exists(path)) { Directory.CreateDirectory(path); } return path; } catch (Exception e) { logger.Error(e); return GetVRChatPhotosLocation(); } } private string GetSteamUserdataPathFromRegistry() { // TODO: Fix Steam userdata path, for now just get the first folder if (Directory.Exists(_steamUserdataPath)) { var steamUserDirs = Directory.GetDirectories(_steamUserdataPath); if (steamUserDirs.Length > 0) { return steamUserDirs[0]; } } return string.Empty; } public override string GetVRChatScreenshotsLocation() { // program files steam userdata screenshots return Path.Join(_steamUserdataPath, "760/remote/438100/screenshots"); } public override bool OpenVrcxAppDataFolder() { var path = Program.AppDataDirectory; if (!Directory.Exists(path)) return false; OpenFolderAndSelectItem(path, true); return true; } public override bool OpenVrcAppDataFolder() { var path = _vrcAppDataPath; if (!Directory.Exists(path)) return false; OpenFolderAndSelectItem(path, true); return true; } public override bool OpenVrcPhotosFolder() { var path = GetVRChatPhotosLocation(); if (!Directory.Exists(path)) return false; OpenFolderAndSelectItem(path, true); return true; } public override bool OpenUGCPhotosFolder(string ugcPath = "") { var path = GetUGCPhotoLocation(ugcPath); if (!Directory.Exists(path)) return false; OpenFolderAndSelectItem(path, true); return true; } public override bool OpenVrcScreenshotsFolder() { var path = GetVRChatScreenshotsLocation(); if (!Directory.Exists(path)) return false; OpenFolderAndSelectItem(path, true); return true; } public override bool OpenCrashVrcCrashDumps() { var path = _vrcCrashesPath; if (!Directory.Exists(path)) return false; OpenFolderAndSelectItem(path, true); return true; } public override void OpenShortcutFolder() { var path = AutoAppLaunchManager.Instance.AppShortcutDirectory; if (!Directory.Exists(path)) return; OpenFolderAndSelectItem(path, true); } public override void OpenFolderAndSelectItem(string path, bool isFolder = false) { if (!File.Exists(path) && !Directory.Exists(path)) return; string directoryPath = isFolder ? path : Path.GetDirectoryName(path); var commandAttempt = new Dictionary { { "nautilus", $"\"{path}\"" }, { "nemo", $"\"{path}\"" }, { "thunar", $"\"{path}\"" }, { "caja", $"--select \"{path}\"" }, { "pcmanfm-qt", $"\"{directoryPath}\"" }, { "pcmanfm", $"\"{directoryPath}\"" }, { "dolphin", $"--select \"{path}\"" }, { "konqueror", $"--select \"{path}\"" }, { "xdg-open", $"\"{directoryPath}\"" } }; foreach (var command in commandAttempt) { if (!IsCommandAvailable(command.Key)) continue; try { var process = new Process { StartInfo = new ProcessStartInfo { FileName = command.Key, Arguments = command.Value, UseShellExecute = false, RedirectStandardError = true, RedirectStandardOutput = true, CreateNoWindow = true, } }; process.Start(); return; // Assume first successful start is enough } catch { // Ignore and try next } } } private bool IsCommandAvailable(string command) { try { var which = new Process { StartInfo = new ProcessStartInfo { FileName = "which", Arguments = command, RedirectStandardOutput = true, UseShellExecute = false, CreateNoWindow = true, } }; which.Start(); string result = which.StandardOutput.ReadToEnd(); which.WaitForExit(); return which.ExitCode == 0 && !string.IsNullOrWhiteSpace(result); } catch { return false; } } public override async Task OpenFolderSelectorDialog(string defaultPath = "") { // TODO: Implement return string.Empty; } public override async Task OpenFileSelectorDialog(string defaultPath = "", string defaultExt = "", string defaultFilter = "All files (*.*)|*.*") { // TODO: Implement return string.Empty; } } }