use std::{
fs::File,
io::{Error, Read, Seek},
path::PathBuf,
sync::LazyLock,
};
use anyhow::Context;
use serde::{Deserialize, Deserializer, Serialize};
#[cfg(target_os = "windows")]
const AMP_PATH: &str = "%Temp%\\VRChat\\VRChat\\amplitude.cache";
#[cfg(target_os = "linux")]
const AMP_PATH: &str = "$HOME/.local/share/Steam/steamapps/compatdata/438100/pfx/drive_c/users/steamuser/AppData/Local/Temp/VRChat/VRChat/amplitude.cache";
#[cfg(target_os = "windows")]
const LOW_PATH: &str = "%AppData%\\..\\LocalLow\\VRChat\\VRChat";
#[cfg(target_os = "linux")]
const LOW_PATH: &str = "$HOME/.local/share/Steam/steamapps/compatdata/438100/pfx/drive_c/users/steamuser/AppData/LocalLow/VRChat/VRChat";
pub static VRCHAT_AMP_PATH: LazyLock<PathBuf> =
LazyLock::new(|| crate::parse_path_env(AMP_PATH).expect("Failed to parse amplitude path"));
pub static VRCHAT_LOW_PATH: LazyLock<PathBuf> =
LazyLock::new(|| crate::parse_path_env(LOW_PATH).expect("Failed to parse local low path"));
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct VRChat {
#[serde(deserialize_with = "deserialize")]
pub cache_directory: PathBuf,
}
pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<PathBuf, D::Error> {
let haystack = String::deserialize(deserializer)?;
let path = crate::parse_path_env(&haystack)
.context("Failed to parse the default path")
.map_err(serde::de::Error::custom)?
.join("Cache-WindowsPlayer");
Ok(path)
}
impl VRChat {
#[must_use]
pub fn get_path() -> PathBuf {
VRCHAT_LOW_PATH.join("config.json")
}
pub fn load() -> Result<Self, Error> {
let path = Self::get_path();
let mut file = File::options()
.read(true)
.write(true)
.create(true)
.truncate(false)
.open(path)?;
let mut text = String::new();
file.read_to_string(&mut text)?;
file.rewind()?;
serde_json::from_str(&text).map_or_else(|_| Ok(Self::default()), Ok)
}
}
impl Default for VRChat {
fn default() -> Self {
Self {
cache_directory: VRCHAT_LOW_PATH.join("Cache-WindowsPlayer"),
}
}
}