mc_launcher_core/command/
builder.rs1use std::path::PathBuf;
9
10use crate::{
11 account::Account,
12 compatibility::{apply_compatibility, CompatibilityPolicy},
13 core::{
14 arguments::{evaluate_arguments, ArgumentContext},
15 classpath::{classpath_entries_for_platform, classpath_string},
16 rules::FeatureSet,
17 version::VersionJson,
18 },
19 platform::{Os, Platform},
20 LauncherError, Result,
21};
22
23#[derive(Debug, Clone)]
30pub struct LaunchOptions {
31 pub account: Account,
33 pub java_executable: Option<PathBuf>,
37 pub game_directory: Option<PathBuf>,
41 pub natives_directory: Option<PathBuf>,
45 pub launcher_name: String,
47 pub launcher_version: String,
49 pub custom_resolution: Option<(u32, u32)>,
51 pub demo: bool,
53 pub server: Option<(String, Option<u16>)>,
55 pub disable_multiplayer: bool,
57 pub disable_chat: bool,
59 pub compatibility: CompatibilityPolicy,
61}
62
63impl Default for LaunchOptions {
64 fn default() -> Self {
65 Self {
66 account: Account::offline("Steve"),
67 java_executable: None,
68 game_directory: None,
69 natives_directory: None,
70 launcher_name: "mc-launcher-core".to_string(),
71 launcher_version: env!("CARGO_PKG_VERSION").to_string(),
72 custom_resolution: None,
73 demo: false,
74 server: None,
75 disable_multiplayer: false,
76 disable_chat: false,
77 compatibility: CompatibilityPolicy::Auto,
78 }
79 }
80}
81
82#[derive(Debug, Clone, PartialEq, Eq)]
87pub struct LaunchCommand {
88 pub executable: PathBuf,
90 pub args: Vec<String>,
92 pub working_dir: PathBuf,
94 pub env: Vec<(String, String)>,
96}
97
98impl LaunchCommand {
99 pub fn to_process_parts(&self) -> (PathBuf, Vec<String>) {
102 (self.executable.clone(), self.args.clone())
103 }
104}
105
106pub fn build_launch_command(
116 version: &VersionJson,
117 minecraft_dir: PathBuf,
118 options: LaunchOptions,
119) -> Result<LaunchCommand> {
120 build_launch_command_for_platform(version, minecraft_dir, options, Platform::current())
121}
122
123pub fn build_launch_command_for_platform(
134 version: &VersionJson,
135 minecraft_dir: PathBuf,
136 options: LaunchOptions,
137 platform: Platform,
138) -> Result<LaunchCommand> {
139 let compatibility = apply_compatibility(version, platform, options.compatibility);
140 let version = &compatibility.version;
141 let version_id = version
142 .id
143 .as_deref()
144 .ok_or_else(|| LauncherError::MissingField {
145 context: "version json".to_string(),
146 field: "id".to_string(),
147 })?;
148 let main_class = version
149 .main_class
150 .clone()
151 .ok_or_else(|| LauncherError::MissingField {
152 context: version_id.to_string(),
153 field: "mainClass".to_string(),
154 })?;
155
156 let game_dir = options
157 .game_directory
158 .clone()
159 .unwrap_or_else(|| minecraft_dir.join("versions").join(version_id));
160 let natives_dir = options.natives_directory.clone().unwrap_or_else(|| {
161 minecraft_dir
162 .join("versions")
163 .join(version_id)
164 .join("natives")
165 });
166 let entries = classpath_entries_for_platform(version, &minecraft_dir, platform)?;
167 let classpath = classpath_string(&entries);
168 let assets_index = version.assets.as_deref().unwrap_or(version_id);
169 let version_type = version.r#type.as_deref().unwrap_or("release");
170
171 let features = FeatureSet {
172 demo_user: options.demo,
173 custom_resolution: options.custom_resolution.is_some(),
174 ..Default::default()
175 };
176 let context = ArgumentContext {
177 minecraft_dir: &minecraft_dir,
178 natives_dir: &natives_dir,
179 game_dir: &game_dir,
180 version,
181 account: &options.account,
182 classpath: &classpath,
183 launcher_name: &options.launcher_name,
184 launcher_version: &options.launcher_version,
185 version_type,
186 assets_index,
187 extra: Default::default(),
188 };
189
190 let executable = options
191 .java_executable
192 .unwrap_or_else(|| PathBuf::from("java"));
193 let mut args = evaluate_arguments(&version.arguments.jvm, &context, &features, platform);
194 if args.is_empty() {
195 args.extend(default_legacy_jvm_arguments(
196 &natives_dir,
197 &classpath,
198 platform,
199 ));
200 }
201 args.push(main_class);
202
203 if version.minecraft_arguments.is_some() {
204 let legacy = version
205 .minecraft_arguments
206 .as_deref()
207 .unwrap_or_default()
208 .split(' ')
209 .map(|part| crate::core::arguments::replace_placeholders(part, &context));
210 args.extend(legacy);
211 } else {
212 args.extend(evaluate_arguments(
213 &version.arguments.game,
214 &context,
215 &features,
216 platform,
217 ));
218 }
219
220 if let Some((width, height)) = options.custom_resolution {
221 args.extend([
222 "--width".to_string(),
223 width.to_string(),
224 "--height".to_string(),
225 height.to_string(),
226 ]);
227 }
228 if options.demo {
229 args.push("--demo".to_string());
230 }
231 if let Some((server, port)) = options.server {
232 args.extend(["--server".to_string(), server]);
233 if let Some(port) = port {
234 args.extend(["--port".to_string(), port.to_string()]);
235 }
236 }
237 if options.disable_multiplayer {
238 args.push("--disableMultiplayer".to_string());
239 }
240 if options.disable_chat {
241 args.push("--disableChat".to_string());
242 }
243
244 Ok(LaunchCommand {
245 executable,
246 args,
247 working_dir: game_dir,
248 env: Vec::new(),
249 })
250}
251
252fn default_legacy_jvm_arguments(
253 natives_dir: &std::path::Path,
254 classpath: &str,
255 platform: Platform,
256) -> Vec<String> {
257 let mut args = Vec::new();
258 if platform.os == Os::MacOs {
259 args.push("-XstartOnFirstThread".to_string());
260 }
261 args.push(format!("-Djava.library.path={}", natives_dir.display()));
262 args.push("-cp".to_string());
263 args.push(classpath.to_string());
264 args
265}