mcvm_core/launch/client/
args.rs1use mcvm_shared::output::{MCVMOutput, MessageContents, MessageLevel};
2use mcvm_shared::util::{ARCH_STRING, OS_STRING};
3use mcvm_shared::versions::VersionPattern;
4
5use crate::instance::{InstanceKind, WindowResolution};
6use crate::launch::{LaunchParameters, QuickPlayType};
7
8use crate::io::files::paths::Paths;
9use crate::net::game_files::assets::get_virtual_dir_path;
10use crate::net::game_files::client_meta::args::ArgumentItem;
11use crate::user::UserKind;
12
13pub(crate) fn process_arg(arg: &ArgumentItem, params: &LaunchParameters) -> Vec<String> {
15 let mut out = Vec::new();
16 let InstanceKind::Client { window } = ¶ms.side else {
17 panic!("Instance is not a client")
18 };
19 match arg {
20 ArgumentItem::Simple(arg) => {
21 let arg = process_simple_arg(arg, params);
22 if let Some(arg) = arg {
23 out.push(arg);
24 }
25 }
26 ArgumentItem::Conditional(arg) => {
27 for rule in &arg.rules {
28 let allowed = rule.action.is_allowed();
29
30 if let Some(os_name) = &rule.os.name {
31 if allowed != (OS_STRING == os_name.to_string()) {
32 return vec![];
33 }
34 }
35 if let Some(os_arch) = &rule.os.arch {
36 if allowed != (ARCH_STRING == os_arch.to_string()) {
37 return vec![];
38 }
39 }
40
41 if let Some(has_custom_resolution) = &rule.features.has_custom_resolution {
42 if *has_custom_resolution && window.resolution.is_none() {
43 return vec![];
44 }
45 }
46 if let Some(is_demo_user) = &rule.features.is_demo_user {
47 if *is_demo_user {
48 let use_demo = match params.users.get_chosen_user() {
49 Some(user) => matches!(user.kind, UserKind::Demo),
50 None => false,
51 };
52 if !use_demo {
53 return vec![];
54 }
55 }
56 }
57 if let Some(quick_play_support) = &rule.features.has_quick_play_support {
58 if *quick_play_support {
59 let uses_quick_play =
60 !matches!(params.launch_config.quick_play, QuickPlayType::None);
61 if !uses_quick_play {
62 return vec![];
63 }
64 }
65 }
66 if let Some(quick_play_singleplayer) = &rule.features.is_quick_play_singleplayer {
67 if *quick_play_singleplayer {
68 let uses_quick_play =
69 !matches!(params.launch_config.quick_play, QuickPlayType::World { .. });
70 if !uses_quick_play {
71 return vec![];
72 }
73 }
74 }
75 if let Some(quick_play_multiplayer) = &rule.features.is_quick_play_multiplayer {
76 if *quick_play_multiplayer {
77 let uses_quick_play = !matches!(
78 params.launch_config.quick_play,
79 QuickPlayType::Server { .. }
80 );
81 if !uses_quick_play {
82 return vec![];
83 }
84 }
85 }
86 if let Some(quick_play_realms) = &rule.features.is_quick_play_realms {
87 if *quick_play_realms {
88 let uses_quick_play =
89 !matches!(params.launch_config.quick_play, QuickPlayType::Realm { .. });
90 if !uses_quick_play {
91 return vec![];
92 }
93 }
94 }
95 }
96
97 for arg in arg.value.iter() {
98 out.extend(process_simple_arg(arg, params));
99 }
100 }
101 };
102
103 out
104}
105
106pub(crate) fn process_simple_arg(arg: &str, params: &LaunchParameters) -> Option<String> {
108 replace_arg_placeholders(arg, params)
109}
110
111macro_rules! placeholder {
113 ($name:expr) => {
114 concat!("${", $name, "}")
115 };
116}
117
118pub(crate) fn replace_arg_placeholders(arg: &str, params: &LaunchParameters) -> Option<String> {
120 let mut out = arg.replace(
122 placeholder!("launcher_name"),
123 ¶ms.branding.launcher_name,
124 );
125 out = out.replace(
126 placeholder!("launcher_version"),
127 ¶ms.branding.launcher_version,
128 );
129
130 out = out.replace(placeholder!("classpath"), ¶ms.classpath.get_str());
132 out = out.replace(
133 placeholder!("natives_directory"),
134 ¶ms
135 .paths
136 .internal
137 .join("versions")
138 .join(params.version.to_string())
139 .join("natives")
140 .to_string_lossy()
141 .to_string(),
142 );
143 out = out.replace(placeholder!("version_name"), params.version);
144 out = out.replace(placeholder!("version_type"), "mcvm");
145 out = out.replace(
146 placeholder!("game_directory"),
147 ¶ms.launch_dir.to_string_lossy().to_string(),
148 );
149 out = out.replace(
150 placeholder!("assets_root"),
151 ¶ms.paths.assets.to_string_lossy().to_string(),
152 );
153 out = out.replace(placeholder!("assets_index_name"), params.version);
154 out = out.replace(
155 placeholder!("game_assets"),
156 &get_virtual_dir_path(params.paths)
157 .to_string_lossy()
158 .to_string(),
159 );
160
161 out = out.replace(placeholder!("clientid"), "mcvm");
162 out = out.replace(placeholder!("user_properties"), "\"\"");
164
165 let InstanceKind::Client { window } = ¶ms.side else {
167 panic!("Instance is not a client")
168 };
169 if let Some(WindowResolution { width, height }) = window.resolution {
170 out = out.replace(placeholder!("resolution_width"), &width.to_string());
171 out = out.replace(placeholder!("resolution_height"), &height.to_string());
172 }
173
174 out = out.replace(placeholder!("quickPlayPath"), "quickPlay/log.json");
176 out = out.replace(
177 placeholder!("quickPlaySingleplayer"),
178 if let QuickPlayType::World { world } = ¶ms.launch_config.quick_play {
179 world
180 } else {
181 ""
182 },
183 );
184 out = out.replace(
185 placeholder!("quickPlayMultiplayer"),
186 &if let QuickPlayType::Server { server, port } = ¶ms.launch_config.quick_play {
187 if let Some(port) = port {
188 format!("{server}:{port}")
189 } else {
190 server.clone()
191 }
192 } else {
193 String::new()
194 },
195 );
196 out = out.replace(
197 placeholder!("quickPlayRealms"),
198 if let QuickPlayType::Realm { realm } = ¶ms.launch_config.quick_play {
199 realm
200 } else {
201 ""
202 },
203 );
204
205 match params.users.get_chosen_user() {
207 Some(user) => {
208 let user_type = match user.get_kind() {
210 UserKind::Microsoft { .. } => "msa",
211 _ => "msa",
212 };
213 out = out.replace(placeholder!("user_type"), user_type);
214
215 if let Some(username) = user.get_name() {
216 out = out.replace(placeholder!("auth_player_name"), username);
217 }
218 if let Some(uuid) = user.get_uuid() {
219 out = out.replace(placeholder!("auth_uuid"), uuid);
220 }
221 if let Some(access_token) = user.get_access_token() {
222 out = out.replace(placeholder!("auth_access_token"), &access_token.0);
223 }
224 if let UserKind::Microsoft {
225 xbox_uid: Some(xbox_uid),
226 } = &user.kind
227 {
228 out = out.replace(placeholder!("auth_xuid"), xbox_uid);
229 }
230
231 if out.contains(placeholder!("auth_player_name"))
233 || out.contains(placeholder!("auth_access_token"))
234 || out.contains(placeholder!("auth_uuid"))
235 || out.contains(placeholder!("auth_xuid"))
236 {
237 return Some(String::new());
238 }
239 }
240 None => {
241 if out.contains(placeholder!("auth_player_name")) {
242 return Some("UnknownUser".into());
243 }
244 if out.contains(placeholder!("auth_access_token"))
245 || out.contains(placeholder!("auth_uuid"))
246 {
247 return Some(String::new());
248 }
249 }
250 }
251
252 Some(out)
253}
254
255pub fn create_quick_play_args(
257 quick_play: &QuickPlayType,
258 version: &str,
259 version_list: &[String],
260 o: &mut impl MCVMOutput,
261) -> Vec<String> {
262 let mut out = Vec::new();
263
264 match quick_play {
265 QuickPlayType::World { .. }
266 | QuickPlayType::Realm { .. }
267 | QuickPlayType::Server { .. } => {
268 let before_23w14a =
269 VersionPattern::Before("23w13a".into()).matches_single(version, version_list);
270 match quick_play {
271 QuickPlayType::None => {}
272 QuickPlayType::World { .. } => {
273 if before_23w14a {
274 o.display(
275 MessageContents::Warning(
276 "World Quick Play has no effect before 23w14a (1.20)".into(),
277 ),
278 MessageLevel::Important,
279 );
280 }
281 }
282 QuickPlayType::Realm { .. } => {
283 if before_23w14a {
284 o.display(
285 MessageContents::Warning(
286 "Realm Quick Play has no effect before 23w14a (1.20)".into(),
287 ),
288 MessageLevel::Important,
289 );
290 }
291 }
292 QuickPlayType::Server { server, port } => {
293 if before_23w14a {
294 out.push("--server".into());
295 out.push(server.clone());
296 if let Some(port) = port {
297 out.push("--port".into());
298 out.push(port.to_string());
299 }
300 }
301 }
302 }
303 }
304 _ => {}
305 }
306
307 out
308}
309
310pub fn fill_logging_path_arg(arg: String, version: &str, paths: &Paths) -> Option<String> {
312 let path = crate::net::game_files::log_config::get_path(version, paths);
313 Some(arg.replace(placeholder!("path"), path.to_str()?))
314}