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