Files
cstb-next/src-tauri/src/vdf/preset.rs

346 lines
9.9 KiB
Rust
Raw Normal View History

use anyhow::Result;
use base64::engine::general_purpose::STANDARD;
use base64::Engine;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
use std::fs;
use std::path::Path;
2025-03-23 22:41:19 +08:00
use tauri_plugin_http::reqwest::blocking::get;
use walkdir::WalkDir;
use crate::steam;
#[derive(Serialize, Deserialize, Debug)]
pub struct User {
steam_id64: u64,
steam_id32: u32,
account_name: String,
persona_name: String,
recent: i32,
avatar: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct LoginUser {
steam_id64: u64,
steam_id32: u32,
account_name: String,
persona_name: String,
remember_password: String,
wants_offline_mode: String,
skip_offline_mode_warning: String,
allow_auto_login: String,
most_recent: String,
timestamp: String,
avatar: String,
avatar_key: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct LocalUser {
steam_id32: u32,
persona_name: String,
avatar_key: String,
}
2025-03-27 11:30:03 +08:00
#[derive(Serialize, Deserialize, Debug)]
pub struct VideoConfig {
version: String,
vendor_id: String,
device_id: String,
setting_cpu_level: String,
setting_gpu_mem_level: String,
setting_gpu_level: String,
setting_knowndevice: String,
setting_defaultres: String,
setting_defaultresheight: String,
setting_refreshrate_numerator: String,
setting_refreshrate_denominator: String,
setting_fullscreen: String,
setting_coop_fullscreen: String,
setting_nowindowborder: String,
setting_mat_vsync: String,
setting_fullscreen_min_on_focus_loss: String,
setting_high_dpi: String,
auto_config: String,
setting_shaderquality: String,
setting_r_texturefilteringquality: String,
setting_msaa_samples: String,
setting_r_csgo_cmaa_enable: String,
setting_videocfg_shadow_quality: String,
setting_videocfg_dynamic_shadows: String,
setting_videocfg_texture_detail: String,
setting_videocfg_particle_detail: String,
setting_videocfg_ao_detail: String,
setting_videocfg_hdr_detail: String,
setting_videocfg_fsr_detail: String,
setting_monitor_index: String,
setting_r_low_latency: String,
setting_aspectratiomode: String,
}
pub fn parse_login_users(steam_dir: &str) -> Result<Vec<LoginUser>> {
let t_path = Path::new(steam_dir).join("config/loginusers.vdf");
if !t_path.exists() {
return Ok(Vec::new());
}
let data = fs::read_to_string(t_path)?;
let json_data = super::parse::to_json(&data);
let kv: HashMap<String, Value> = serde_json::from_str(&json_data)?;
let mut users = Vec::new();
for (k, v) in kv {
let props = v.as_object().unwrap();
let avatar = if let Some(img) = read_avatar(&steam_dir, &k) {
img
} else {
String::new()
};
let id64 = k.parse::<u64>()?;
let user = LoginUser {
steam_id32: steam::id::id64_to_32(id64),
steam_id64: id64,
account_name: props
.get("AccountName")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string(),
persona_name: props
.get("PersonaName")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string(),
remember_password: props
.get("RememberPassword")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string(),
wants_offline_mode: props
.get("WantsOfflineMode")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string(),
skip_offline_mode_warning: props
.get("SkipOfflineModeWarning")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string(),
allow_auto_login: props
.get("AllowAutoLogin")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string(),
most_recent: props
.get("MostRecent")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string(),
timestamp: props
.get("Timestamp")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string(),
avatar,
avatar_key: String::new(),
};
users.push(user);
}
Ok(users)
}
pub fn parse_local_users(steam_dir: &str) -> Result<Vec<LocalUser>> {
let root = Path::new(steam_dir).join("userdata");
if !root.exists() {
return Ok(Vec::new());
}
let mut local_users = Vec::new();
for entry in WalkDir::new(&root) {
let entry = entry?;
let path = entry.path();
// 跳过根目录
if path == root {
continue;
}
// 只处理目录
if entry.file_type().is_dir() {
let id = path.file_name().unwrap().to_str().unwrap();
// 检查 localconfig.vdf 文件是否存在
let local_config_path = path.join("config/localconfig.vdf");
if !local_config_path.exists() {
continue;
}
// 读取并解析 localconfig.vdf 文件
let data = fs::read_to_string(local_config_path)?;
let json_data = super::parse::to_json(&data);
let kv: HashMap<String, Value> = serde_json::from_str(&json_data)?;
// 获取 friends 节点
let friends = kv.get("friends").and_then(|v| v.as_object());
if friends.is_none() {
continue;
}
let friends = friends.unwrap();
// 获取 PersonaName
let persona_name = friends
.get("PersonaName")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
// 获取 AvatarKey
let avatar_key = friends
.get(id)
.and_then(|v| v.as_object())
.and_then(|props| props.get("avatar"))
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
// 创建 LocalUser 并加入列表
local_users.push(LocalUser {
steam_id32: id.parse::<u32>().unwrap(),
persona_name,
avatar_key,
});
// 跳过子目录
WalkDir::new(path).max_depth(1).into_iter().next();
}
}
Ok(local_users)
}
pub fn merge_users(login: Vec<LoginUser>, local: Vec<LocalUser>) -> Vec<User> {
let mut users = Vec::new();
for i in login {
2025-03-23 22:41:19 +08:00
let avatar: String;
let mut avatar_key = String::new();
2025-03-23 22:41:19 +08:00
// 匹配获取 avatar_key 在线获取头像使用
let t_usr: Vec<&LocalUser> = local
.iter()
2025-03-23 22:41:19 +08:00
.filter(|j| i.steam_id32 == j.steam_id32)
.collect();
2025-03-23 22:41:19 +08:00
if let Some(usr) = t_usr.first() {
avatar_key = usr.avatar_key.clone();
}
2025-03-23 22:41:19 +08:00
// 获取头像的base64 本地头像文件不存在时使用在线api获取
if i.avatar.is_empty() && !avatar_key.is_empty() {
avatar = download_avatar(&avatar_key).unwrap_or_default();
2025-03-23 22:41:19 +08:00
} else {
avatar = i.avatar;
}
users.push(User {
steam_id64: i.steam_id64,
2025-03-23 22:41:19 +08:00
steam_id32: i.steam_id32,
account_name: i.account_name,
persona_name: i.persona_name,
recent: i.most_recent.parse().unwrap_or(0),
avatar,
});
}
// 把第一个recent=1的放在头部
for (id, usr) in users.iter_mut().enumerate() {
if usr.recent == 1 {
let tmp = users.remove(id);
users.insert(0, tmp);
break;
}
}
users
}
2025-03-23 22:41:19 +08:00
pub fn get_users(steam_dir: &str) -> Result<Vec<User>> {
let login_users = parse_login_users(steam_dir)?;
let local_users = parse_local_users(steam_dir)?;
let users = merge_users(login_users, local_users);
Ok(users)
}
fn download_avatar(avatar_key: &str) -> Result<String> {
2025-03-23 22:41:19 +08:00
// 下载并转换成Base64 "https://avatars.cloudflare.steamstatic.com/" + avatarKey + "_full.jpg"
let url = format!(
"https://avatars.cloudflare.steamstatic.com/{}_full.jpg",
avatar_key
);
if let Ok(resp) = get(&url) {
if let Ok(bytes) = resp.bytes() {
return Ok(STANDARD.encode(bytes));
}
}
return Err(anyhow::anyhow!("Failed to download avatar"));
}
fn read_avatar(steam_dir: &str, steam_id64: &str) -> Option<String> {
let t_path = Path::new(steam_dir).join(format!("config\\avatarcache\\{}.png", steam_id64));
if !t_path.exists() {
return None;
}
if let Ok(img) = fs::read(t_path) {
Some(STANDARD.encode(img))
} else {
None
}
}
2025-03-27 11:30:03 +08:00
pub fn get_cs2_video(file_path: &str) -> Result<HashMap<String, String>> {
let data = fs::read_to_string(file_path)?;
let json_data = super::parse::to_json(&data);
let kv: HashMap<String, String> = serde_json::from_str(&json_data)?;
Ok(kv)
}
pub fn set_cs2_video(file_path: &str, data: HashMap<String, String>) -> Result<()> {
let json_data = serde_json::to_string(&data)?;
let vdf_data = super::parse::to_vdf(&json_data);
fs::write(file_path, vdf_data)?;
Ok(())
}
2025-03-23 22:41:19 +08:00
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_users() {
let users = get_users("D:\\Programs\\Steam").unwrap();
println!("{:?}", users);
}
2025-03-27 11:30:03 +08:00
#[test]
fn test_get_cs2_video() {
let video_config = get_cs2_video("src-tauri/src/vdf/tests/cs2_video.txt").unwrap();
println!("{:?}", video_config);
}
#[test]
fn test_set_cs2_video() {
let mut video_config = HashMap::new();
video_config.insert("setting.fullscreen".to_string(), "0".to_string());
set_cs2_video("temp/cs2_video.txt", video_config).unwrap();
}
}