mc_bootstrap/
lib.rs

1pub mod classpath;
2pub mod manifest;
3pub mod rules;
4
5use manifest::{read_manifest_from_file, JvmArgument};
6use rules::is_all_rules_satisfied;
7use std::{path::PathBuf, process::Command};
8use thiserror::Error;
9
10#[derive(Error, Debug)]
11pub enum ClientBootstrapError {
12    #[error("The game directory doesn't exist.")]
13    GameDirNotExist,
14
15    #[error("The java bin doesn't exist.")]
16    JavaBinNotExist,
17
18    #[error("The version file (.json) doesn't exist.")]
19    VersionFileNotFound,
20
21    #[error("An unexpected error has ocurred.")]
22    UnknownError,
23
24    #[error("{0}")]
25    Json(#[from] serde_json::Error),
26}
27
28pub struct ClientAuth {
29    pub access_token: Option<String>,
30    pub username: String,
31    pub uuid: Option<String>,
32}
33
34pub struct ClientVersion {
35    pub version: String,
36    pub version_type: String,
37}
38
39pub struct ClientSettings {
40    pub assets: PathBuf,
41    pub auth: ClientAuth,
42    pub game_dir: PathBuf,
43    pub java_bin: PathBuf,
44    pub libraries_dir: PathBuf,
45    pub manifest_file: PathBuf,
46    pub natives_dir: PathBuf,
47    pub version: ClientVersion,
48    pub version_jar_file: PathBuf,
49}
50
51pub struct ClientBootstrap {
52    pub settings: ClientSettings,
53}
54
55impl ClientBootstrap {
56    pub fn new(settings: ClientSettings) -> Self {
57        Self { settings }
58    }
59
60    pub fn get_assets_dir(&self) -> PathBuf {
61        return self.settings.assets.clone();
62    }
63
64    pub fn get_game_dir(&self) -> PathBuf {
65        return self.settings.game_dir.clone();
66    }
67
68    pub fn get_json_file(&self) -> PathBuf {
69        return self.settings.manifest_file.clone();
70    }
71
72    pub fn get_jar_file(&self) -> PathBuf {
73        return self.settings.version_jar_file.clone();
74    }
75
76    pub fn get_libs_dir(&self) -> PathBuf {
77        return self.settings.libraries_dir.clone();
78    }
79
80    pub fn get_natives_dir(&self) -> PathBuf {
81        return self.settings.natives_dir.clone();
82    }
83
84    pub fn build_args(&self) -> Result<Vec<String>, ClientBootstrapError> {
85        let auth = &self.settings.auth;
86        let assets_dir = self.get_assets_dir();
87        let game_dir = self.get_game_dir();
88        let java_bin = self.settings.java_bin.clone();
89        let json_file = self.get_json_file();
90        let natives_dir = self.get_natives_dir();
91        let version = &self.settings.version;
92
93        if !game_dir.is_dir() {
94            return Err(ClientBootstrapError::GameDirNotExist);
95        }
96
97        if !java_bin.is_file() {
98            return Err(ClientBootstrapError::JavaBinNotExist);
99        }
100
101        if !json_file.is_file() {
102            return Err(ClientBootstrapError::VersionFileNotFound);
103        }
104
105        let manifest = read_manifest_from_file(json_file).unwrap();
106
107        let assets_index = &manifest.asset_index.id;
108        let classpath = classpath::create_classpath(
109            self.get_jar_file(),
110            self.get_libs_dir(),
111            manifest.libraries,
112        );
113
114        let mut args: Vec<String> = vec![];
115
116        for arg in manifest.arguments.jvm {
117            match arg {
118                JvmArgument::String(value) => {
119                    args.push(value);
120                }
121                JvmArgument::Struct { value, rules, .. } => {
122                    if !is_all_rules_satisfied(&rules) {
123                        continue;
124                    }
125
126                    if let Some(value) = value.as_str() {
127                        args.push(value.to_string());
128                    } else if let Some(value_arr) = value.as_array() {
129                        for value in value_arr {
130                            if let Some(value) = value.as_str() {
131                                args.push(value.to_string());
132                            }
133                        }
134                    }
135                }
136            }
137        }
138
139        args.push(manifest.main_class);
140
141        for arg in manifest.arguments.game {
142            match arg {
143                JvmArgument::String(value) => {
144                    args.push(value);
145                }
146                JvmArgument::Struct { value, rules, .. } => {
147                    if !is_all_rules_satisfied(&rules) {
148                        continue;
149                    }
150
151                    if let Some(value) = value.as_str() {
152                        args.push(value.to_string());
153                    } else if let Some(value_arr) = value.as_array() {
154                        for value in value_arr {
155                            if let Some(value) = value.as_str() {
156                                args.push(value.to_string());
157                            }
158                        }
159                    }
160                }
161            }
162        }
163
164        args = args
165            .iter()
166            .map(|x| {
167                x.replace("${assets_root}", &assets_dir.to_str().unwrap())
168                    .replace("${game_directory}", &game_dir.to_str().unwrap())
169                    .replace("${natives_directory}", &natives_dir.to_str().unwrap())
170                    .replace("${launcher_name}", "minecraft-rs/bootstrap")
171                    .replace("${launcher_version}", "0.1.1")
172                    .replace(
173                        "${auth_access_token}",
174                        auth.access_token
175                            .clone()
176                            .unwrap_or("null".to_string())
177                            .as_str(),
178                    )
179                    .replace("${auth_player_name}", auth.username.as_str())
180                    .replace(
181                        "${auth_uuid}",
182                        auth.uuid.clone().unwrap_or("null".to_string()).as_str(),
183                    )
184                    .replace("${version_type}", &version.version_type)
185                    .replace("${version_name}", &version.version)
186                    .replace("${assets_index_name}", &assets_index)
187                    .replace("${user_properties}", "{}")
188                    .replace("${classpath}", &classpath)
189            })
190            .collect();
191
192        return Ok(args);
193    }
194
195    pub fn launch(&self) -> Result<i32, ClientBootstrapError> {
196        let args = self.build_args().unwrap();
197
198        let mut process = Command::new(&self.settings.java_bin)
199            .args(args)
200            .current_dir(&self.settings.game_dir)
201            .spawn()
202            .expect("command failed to start");
203
204        let status = process.wait().unwrap().code().unwrap();
205        return Ok(status);
206    }
207}