Skip to main content

vrc_log/
vrchat.rs

1use std::{
2    fs::File,
3    io::{Error, Read, Seek},
4    path::PathBuf,
5    sync::LazyLock,
6};
7
8use anyhow::Context;
9use serde::{Deserialize, Deserializer, Serialize};
10
11#[cfg(target_os = "windows")]
12const AMP_PATH: &str = "%Temp%\\VRChat\\VRChat\\amplitude.cache";
13
14#[cfg(target_os = "linux")]
15const AMP_PATH: &str = "$HOME/.local/share/Steam/steamapps/compatdata/438100/pfx/drive_c/users/steamuser/AppData/Local/Temp/VRChat/VRChat/amplitude.cache";
16
17#[cfg(target_os = "windows")]
18const LOW_PATH: &str = "%AppData%\\..\\LocalLow\\VRChat\\VRChat";
19
20#[cfg(target_os = "linux")]
21const LOW_PATH: &str = "$HOME/.local/share/Steam/steamapps/compatdata/438100/pfx/drive_c/users/steamuser/AppData/LocalLow/VRChat/VRChat";
22
23pub static VRCHAT_AMP_PATH: LazyLock<PathBuf> =
24    LazyLock::new(|| crate::parse_path_env(AMP_PATH).expect("Failed to parse amplitude path"));
25
26/// This is a static path and cannot be changed (without symlinks)
27pub static VRCHAT_LOW_PATH: LazyLock<PathBuf> =
28    LazyLock::new(|| crate::parse_path_env(LOW_PATH).expect("Failed to parse local low path"));
29
30#[derive(Clone, Debug, Deserialize, Serialize)]
31pub struct VRChat {
32    /// This is actually the path to the cache parent directory
33    /// `VRChat` doesn't allow you to change the cache directory name
34    /// The `Cache-WindowsPlayer` path is appended during deserialization below
35    /// Because this is how `VRChat` does it, it must not be in the config file
36    #[serde(deserialize_with = "deserialize")]
37    pub cache_directory: PathBuf,
38}
39
40/// Try to deserialize the `VRChat` `config.json` `cache_directory`, `parse_path_env`, and append `Cache-WindowsPlayer`
41///
42/// # Errors
43/// Will return `Err` if `crate::parse_path_env` errors
44pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<PathBuf, D::Error> {
45    let haystack = String::deserialize(deserializer)?;
46    let path = crate::parse_path_env(&haystack)
47        .context("Failed to parse the default path")
48        .map_err(serde::de::Error::custom)?
49        .join("Cache-WindowsPlayer");
50
51    Ok(path)
52}
53
54impl VRChat {
55    #[must_use]
56    pub fn get_path() -> PathBuf {
57        VRCHAT_LOW_PATH.join("config.json")
58    }
59
60    /// Try to load the `VRChat` `config.json` file for the `cache_directory` field
61    ///
62    /// # Errors
63    /// Will return `Err` if `File::open`, `File::read_to_string`, or `File::rewind` errors
64    pub fn load() -> Result<Self, Error> {
65        let path = Self::get_path();
66        let mut file = File::options()
67            .read(true)
68            .write(true)
69            .create(true)
70            .truncate(false)
71            .open(path)?;
72
73        let mut text = String::new();
74        file.read_to_string(&mut text)?;
75        file.rewind()?;
76
77        // Fallback to default below if config fails to deserialize
78        serde_json::from_str(&text).map_or_else(|_| Ok(Self::default()), Ok)
79    }
80}
81
82impl Default for VRChat {
83    fn default() -> Self {
84        Self {
85            cache_directory: VRCHAT_LOW_PATH.join("Cache-WindowsPlayer"),
86        }
87    }
88}