lighty_launch/launch/builder.rs
1// Copyright (c) 2025 Hamadi
2// Licensed under the MIT License
3
4//! Launch builder for configuring game arguments and JVM options
5
6use std::collections::{HashMap, HashSet};
7use lighty_auth::UserProfile;
8use lighty_java::JavaDistribution;
9use crate::errors::InstallerResult;
10use lighty_loaders::types::{VersionInfo, Loader, LoaderExtensions};
11use crate::arguments::Arguments;
12use crate::installer::Installer;
13
14#[cfg(feature = "events")]
15use lighty_event::EventBus;
16
17/// Launch builder for configuring launch parameters
18///
19/// Created by calling `version.launch(&profile, java_distribution)`
20pub struct LaunchBuilder<'a, T> {
21 pub(crate) version: &'a mut T,
22 pub(crate) profile: &'a UserProfile,
23 pub(crate) java_distribution: JavaDistribution,
24 pub(crate) jvm_overrides: HashMap<String, String>,
25 pub(crate) jvm_removals: HashSet<String>,
26 pub(crate) arg_overrides: HashMap<String, String>,
27 pub(crate) arg_removals: HashSet<String>,
28 pub(crate) raw_args: Vec<String>,
29 #[cfg(feature = "events")]
30 pub(crate) event_bus: Option<&'a EventBus>,
31}
32
33impl<'a, T> LaunchBuilder<'a, T>
34where
35 T: VersionInfo<LoaderType = Loader> + LoaderExtensions + Arguments + Installer,
36{
37 /// Create a new launch builder
38 pub(crate) fn new(
39 version: &'a mut T,
40 profile: &'a UserProfile,
41 java_distribution: JavaDistribution,
42 ) -> Self {
43 Self {
44 version,
45 profile,
46 java_distribution,
47 jvm_overrides: HashMap::new(),
48 jvm_removals: HashSet::new(),
49 arg_overrides: HashMap::new(),
50 arg_removals: HashSet::new(),
51 raw_args: Vec::new(),
52 #[cfg(feature = "events")]
53 event_bus: None,
54 }
55 }
56
57 /// Set an event bus to receive download progress events
58 ///
59 /// # Example
60 /// ```no_run
61 /// let event_bus = EventBus::new(100);
62 /// version.launch(&profile, JavaDistribution::Zulu)
63 /// .with_event_bus(&event_bus)
64 /// .run()
65 /// .await?;
66 /// ```
67 #[cfg(feature = "events")]
68 pub fn with_event_bus(mut self, event_bus: &'a EventBus) -> Self {
69 self.event_bus = Some(event_bus);
70 self
71 }
72
73 /// Configure JVM options
74 ///
75 /// # Example
76 /// ```no_run
77 /// version.launch(&profile, JavaDistribution::Zulu)
78 /// .with_jvm_options()
79 /// .set("Xmx", "4G")
80 /// .set("Xms", "2G")
81 /// .set("XX:+UseG1GC", "")
82 /// .done()
83 /// .run()
84 /// .await
85 /// ```
86 pub fn with_jvm_options(self) -> JvmOptionsBuilder<'a, T> {
87 JvmOptionsBuilder {
88 parent: self,
89 overrides: HashMap::new(),
90 removals: HashSet::new(),
91 }
92 }
93
94 /// Configure game arguments
95 ///
96 /// # Example
97 /// ```no_run
98 /// version.launch(&profile, JavaDistribution::Zulu)
99 /// .with_arguments()
100 /// .set(KEY_WIDTH, "1920")
101 /// .set(KEY_HEIGHT, "1080")
102 /// .done()
103 /// .run()
104 /// .await
105 /// ```
106 pub fn with_arguments(self) -> ArgumentsBuilder<'a, T> {
107 ArgumentsBuilder {
108 parent: self,
109 overrides: HashMap::new(),
110 removals: HashSet::new(),
111 raw_args: Vec::new(),
112 }
113 }
114
115 /// Execute the launch
116 ///
117 /// # Example
118 /// ```no_run
119 /// version.launch(&profile, JavaDistribution::Zulu).run().await?;
120 /// ```
121 pub async fn run(self) -> InstallerResult<()> {
122 crate::launch::execute_launch(
123 self.version,
124 self.profile,
125 self.java_distribution,
126 &self.jvm_overrides,
127 &self.jvm_removals,
128 &self.arg_overrides,
129 &self.arg_removals,
130 &self.raw_args,
131 #[cfg(feature = "events")]
132 self.event_bus,
133 )
134 .await
135 }
136}
137
138/// JVM options builder
139///
140/// Configure JVM options like memory, garbage collection, etc.
141pub struct JvmOptionsBuilder<'a, T> {
142 parent: LaunchBuilder<'a, T>,
143 overrides: HashMap<String, String>,
144 removals: HashSet<String>,
145}
146
147impl<'a, T> JvmOptionsBuilder<'a, T>
148where
149 T: VersionInfo<LoaderType = Loader> + LoaderExtensions + Arguments + Installer,
150{
151 /// Set a JVM option
152 ///
153 /// The `-` prefix is added automatically based on the key format:
154 /// - `Xmx`, `Xms` → `-Xmx`, `-Xms`
155 /// - `XX:+UseG1GC` → `-XX:+UseG1GC`
156 /// - `Djava.library.path` → `-Djava.library.path`
157 ///
158 /// # Arguments
159 /// - `key`: JVM option key (without the `-` prefix)
160 /// - `value`: Option value (empty string for flags)
161 ///
162 /// # Example
163 /// ```no_run
164 /// .set("Xmx", "4G") // → -Xmx4G
165 /// .set("Xms", "2G") // → -Xms2G
166 /// .set("XX:+UseG1GC", "") // → -XX:+UseG1GC
167 /// .set("Djava.library.path", "/path") // → -Djava.library.path=/path
168 /// ```
169 pub fn set(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
170 self.overrides.insert(key.into(), value.into());
171 self
172 }
173
174 /// Remove a JVM option
175 ///
176 /// # Arguments
177 /// - `key`: JVM option key to remove
178 pub fn remove(mut self, key: impl Into<String>) -> Self {
179 self.removals.insert(key.into());
180 self
181 }
182
183 /// Finish configuring JVM options and return to the launch builder
184 pub fn done(self) -> LaunchBuilder<'a, T> {
185 let mut parent = self.parent;
186 parent.jvm_overrides = self.overrides;
187 parent.jvm_removals = self.removals;
188 parent
189 }
190}
191
192/// Game arguments builder
193///
194/// Configure game arguments like resolution, game directory, etc.
195pub struct ArgumentsBuilder<'a, T> {
196 parent: LaunchBuilder<'a, T>,
197 overrides: HashMap<String, String>,
198 removals: HashSet<String>,
199 raw_args: Vec<String>,
200}
201
202impl<'a, T> ArgumentsBuilder<'a, T>
203where
204 T: VersionInfo<LoaderType = Loader> + LoaderExtensions + Arguments + Installer,
205{
206 /// Set a game argument or placeholder value
207 ///
208 /// This method intelligently handles two cases:
209 /// - If the key is a known placeholder constant (like KEY_LAUNCHER_NAME), it overrides the placeholder value
210 /// - Otherwise, it adds a raw argument with automatic `--` prefix
211 ///
212 /// # Arguments
213 /// - `key`: Either a placeholder constant or a custom argument name
214 /// - `value`: The value for the argument
215 ///
216 /// # Example
217 /// ```no_run
218 /// use lighty_launch::arguments::KEY_LAUNCHER_NAME;
219 ///
220 /// .set(KEY_LAUNCHER_NAME, "MyLauncher") // Override ${launcher_name}
221 /// .set("width", "1920") // Adds --width 1920
222 /// .set("height", "1080") // Adds --height 1080
223 /// .set("fullscreen", "") // Adds --fullscreen (no value)
224 /// ```
225 pub fn set(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
226 let key_str = key.into();
227 let value_str = value.into();
228
229 // Liste des clés de placeholders connues
230 const KNOWN_PLACEHOLDERS: &[&str] = &[
231 "auth_player_name", "auth_uuid", "auth_access_token", "auth_xuid",
232 "clientid", "user_type", "user_properties",
233 "version_name", "version_type",
234 "game_directory", "assets_root", "natives_directory", "library_directory",
235 "assets_index_name", "launcher_name", "launcher_version",
236 "classpath", "classpath_separator",
237 ];
238
239 // Si c'est un placeholder connu, override
240 if KNOWN_PLACEHOLDERS.contains(&key_str.as_str()) {
241 self.overrides.insert(key_str, value_str);
242 } else {
243 // Sinon, ajouter comme argument brut avec préfixe --
244 let formatted_arg = if key_str.starts_with("--") {
245 key_str
246 } else if key_str.starts_with('-') {
247 format!("-{}", key_str)
248 } else {
249 format!("--{}", key_str)
250 };
251
252 self.raw_args.push(formatted_arg);
253
254 if !value_str.is_empty() {
255 self.raw_args.push(value_str);
256 }
257 }
258
259 self
260 }
261
262 /// Remove a game argument
263 ///
264 /// # Arguments
265 /// - `key`: Argument key to remove
266 pub fn remove(mut self, key: impl Into<String>) -> Self {
267 self.removals.insert(key.into());
268 self
269 }
270
271 /// Finish configuring arguments and return to the launch builder
272 pub fn done(self) -> LaunchBuilder<'a, T> {
273 let mut parent = self.parent;
274 parent.arg_overrides = self.overrides;
275 parent.arg_removals = self.removals;
276 parent.raw_args = self.raw_args;
277 parent
278 }
279}
280