mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-13 03:43:50 +02:00
360 lines
13 KiB
C#
360 lines
13 KiB
C#
using System;
|
|
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.Windows.Forms;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace VRCX
|
|
{
|
|
public partial class AppApiCef
|
|
{
|
|
public override string GetVRChatAppDataLocation()
|
|
{
|
|
return Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + @"Low\VRChat\VRChat";
|
|
}
|
|
|
|
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<JObject>(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(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), "VRChat");
|
|
try
|
|
{
|
|
var json = ReadConfigFile();
|
|
if (string.IsNullOrEmpty(json))
|
|
return defaultPath;
|
|
|
|
var obj = JsonConvert.DeserializeObject<JObject>(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()
|
|
{
|
|
string steamUserdataPath = Path.Join(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.Join(o.ToString(), @"userdata");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
logger.Error($"Failed to get Steam userdata path from registry: {e}");
|
|
}
|
|
|
|
return steamUserdataPath;
|
|
}
|
|
|
|
public override 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.Join(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;
|
|
}
|
|
|
|
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 = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + @"Low\VRChat\VRChat";
|
|
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 = Path.Join(Path.GetTempPath(), "VRChat", "VRChat", "Crashes");
|
|
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)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
|
|
private 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}\"");
|
|
}
|
|
}
|
|
|
|
public override async Task<string> OpenFolderSelectorDialog(string defaultPath = "")
|
|
{
|
|
var tcs = new TaskCompletionSource<string>();
|
|
var staThread = new Thread(() =>
|
|
{
|
|
try
|
|
{
|
|
using var openFolderDialog = new FolderBrowserDialog();
|
|
openFolderDialog.InitialDirectory = Directory.Exists(defaultPath) ? defaultPath : GetVRChatPhotosLocation();
|
|
|
|
var dialogResult = openFolderDialog.ShowDialog(MainForm.nativeWindow);
|
|
if (dialogResult == DialogResult.OK)
|
|
{
|
|
tcs.SetResult(openFolderDialog.SelectedPath);
|
|
}
|
|
else
|
|
{
|
|
tcs.SetResult(defaultPath);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
tcs.SetException(ex);
|
|
}
|
|
});
|
|
|
|
staThread.SetApartmentState(ApartmentState.STA);
|
|
staThread.Start();
|
|
|
|
return await tcs.Task;
|
|
}
|
|
|
|
public override async Task<string> OpenFileSelectorDialog(string defaultPath = "", string defaultExt = "", string defaultFilter = "All files (*.*)|*.*")
|
|
{
|
|
var tcs = new TaskCompletionSource<string>();
|
|
var staThread = new Thread(() =>
|
|
{
|
|
try
|
|
{
|
|
using (var openFileDialog = new System.Windows.Forms.OpenFileDialog())
|
|
{
|
|
if (Directory.Exists(defaultPath))
|
|
{
|
|
openFileDialog.InitialDirectory = defaultPath;
|
|
}
|
|
|
|
openFileDialog.DefaultExt = defaultExt;
|
|
openFileDialog.Filter = defaultFilter;
|
|
|
|
var dialogResult = openFileDialog.ShowDialog(MainForm.nativeWindow);
|
|
if (dialogResult == DialogResult.OK && !string.IsNullOrEmpty(openFileDialog.FileName))
|
|
{
|
|
tcs.SetResult(openFileDialog.FileName);
|
|
}
|
|
else
|
|
{
|
|
tcs.SetResult("");
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
tcs.SetException(ex);
|
|
}
|
|
});
|
|
|
|
staThread.SetApartmentState(ApartmentState.STA);
|
|
staThread.Start();
|
|
|
|
return await tcs.Task;
|
|
}
|
|
}
|
|
} |