1
use std::{borrow::Cow, fs, path::Path};
2

            
3
use home::home_dir;
4
use keyvalues_parser::{Value, Vdf};
5
use rust_search::SearchBuilder;
6

            
7
pub struct Steam {
8
    pub localconfig: String,
9
}
10

            
11
impl Steam {
12
    pub fn new() -> Self {
13
        Steam {
14
            localconfig: Steam::discover_localconfig(),
15
        }
16
    }
17

            
18
    fn discover_localconfig() -> String {
19
        let home = home_dir().unwrap();
20
        let mut home = home.to_str().unwrap();
21

            
22
        if home == "/root" {
23
            // Fallback to checking all home folders when running as root
24
            home = "/home";
25
        }
26

            
27
        let search: Vec<String> = SearchBuilder::default()
28
            .location(home)
29
            .search_input("localconfig")
30
            .limit(1)
31
            .ext("vdf")
32
            .strict()
33
            .hidden()
34
            .build()
35
            .collect();
36

            
37
        if search.len() == 0 {
38
            panic!("Failed to find localconfig.vdf!");
39
        }
40

            
41
        search.first().unwrap().to_owned()
42
    }
43

            
44
    pub fn get_hash(&self) -> String {
45
        let bytes = std::fs::read(Path::new(&self.localconfig)).unwrap();
46
        return sha256::digest(&bytes);
47
    }
48

            
49
    pub fn app_ids(&self) -> Option<Vec<String>> {
50
        let contents = fs::read_to_string(&self.localconfig).unwrap();
51
        let vdf = Vdf::parse(&contents).expect("Failed to parse VDF file");
52

            
53
        let apps = vdf
54
            .value
55
            .get_obj()?
56
            .get("Software")?
57
            .get(0)?
58
            .get_obj()?
59
            .get("Valve")?
60
            .get(0)?
61
            .get_obj()?
62
            .get("Steam")?
63
            .get(0)?
64
            .get_obj()?
65
            .get("apps")?
66
            .get(0)?
67
            .get_obj()?
68
            .iter();
69

            
70
        let mut app_ids: Vec<String> = vec![];
71
        for (app_id, _) in apps {
72
            app_ids.push(app_id.to_string());
73
        }
74

            
75
        return Some(app_ids);
76
    }
77

            
78
    pub fn update_all_launch_options<F: Fn(Option<String>) -> String>(
79
        &self,
80
        app_ids: Vec<String>,
81
        get_launch_options: F,
82
    ) -> Option<()> {
83
        let contents = fs::read_to_string(&self.localconfig).unwrap();
84
        let mut vdf = Vdf::parse(&contents).expect("Failed to parse VDF file");
85

            
86
        let apps = vdf
87
            .value
88
            .get_mut_obj()?
89
            .get_mut("Software")?
90
            .get_mut(0)?
91
            .get_mut_obj()?
92
            .get_mut("Valve")?
93
            .get_mut(0)?
94
            .get_mut_obj()?
95
            .get_mut("Steam")?
96
            .get_mut(0)?
97
            .get_mut_obj()?
98
            .get_mut("apps")?
99
            .get_mut(0)?
100
            .get_mut_obj()?;
101

            
102
        for app_id in app_ids {
103
            let app = apps.get_mut(app_id.as_str())?.get_mut(0)?.get_mut_obj()?;
104

            
105
            let launch_options = if app.contains_key("LaunchOptions") {
106
                app.get_mut("LaunchOptions")?
107
                    .get_mut(0)?
108
                    .get_mut_str()
109
                    .map(|value| value.to_string())
110
            } else {
111
                None
112
            };
113

            
114
            app.insert(
115
                Cow::from("LaunchOptions"),
116
                vec![Value::Str(Cow::from(get_launch_options(launch_options)))],
117
            );
118
        }
119

            
120
        fs::write(&self.localconfig, vdf.to_string()).expect("Failed to write file");
121

            
122
        Some(())
123
    }
124

            
125
    pub fn update_launch_options<F: Fn(Option<String>) -> String>(
126
        &self,
127
        app_id: String,
128
        get_launch_options: F,
129
    ) -> Option<()> {
130
        self.update_all_launch_options(vec![app_id], get_launch_options)
131
    }
132
}