lighty_launch/launch/
runner.rs

1use lighty_core::time_it;
2use lighty_java::jre_downloader::jre_download;
3use lighty_java::{JavaDistribution, JreError};
4use lighty_loaders::types::version_metadata::Version;
5use crate::errors::{InstallerError, InstallerResult};
6use crate::installer::Installer;
7use super::builder::LaunchBuilder;
8use lighty_loaders::types::{Loader, LoaderExtensions, VersionInfo};
9use lighty_auth::UserProfile;
10use std::sync::Arc;
11use std::path::PathBuf;
12use lighty_loaders::types::version_metadata::VersionMetaData;
13use tokio::sync::oneshot;
14use lighty_java::jre_downloader::find_java_binary;
15use lighty_java::runtime::JavaRuntime;
16use crate::arguments::Arguments;
17use std::collections::{HashMap,HashSet};
18
19#[cfg(feature = "events")]
20use lighty_event::EventBus;
21
22pub trait Launch {
23    /// Launch the game with a builder pattern
24    ///
25    /// # Arguments
26    /// - `profile`: User profile from authentication
27    /// - `java_distribution`: Java distribution to use
28    ///
29    /// # Returns
30    /// A `LaunchBuilder` for configuring JVM options and game arguments
31    ///
32    /// # Example
33    /// ```no_run
34    /// // Simple launch
35    /// version.launch(&profile, JavaDistribution::Zulu).await?;
36    ///
37    /// // With custom options
38    /// version.launch(&profile, JavaDistribution::Zulu)
39    ///     .with_jvm_options()
40    ///         .set("Xmx", "4G")
41    ///         .done()
42    ///     .with_arguments()
43    ///         .set(KEY_WIDTH, "1920")
44    ///         .done()
45    ///     .await?;
46    /// ```
47    fn launch<'a>(&'a mut self, profile: &'a UserProfile, java_distribution: JavaDistribution) -> LaunchBuilder<'a, Self>
48    where
49        Self: Sized;
50}
51
52// Implémentation générique pour tout type implémentant VersionInfo + les traits nécessaires
53impl<T> Launch for T
54where
55    T: VersionInfo<LoaderType = Loader> + LoaderExtensions + Arguments + Installer,
56{
57    fn launch<'a>(&'a mut self, profile: &'a UserProfile, java_distribution: JavaDistribution) -> LaunchBuilder<'a, Self> {
58        LaunchBuilder::new(self, profile, java_distribution)
59    }
60}
61
62/// Internal function to execute the launch process
63pub(crate) async fn execute_launch<T>(
64    version: &mut T,
65    profile: &UserProfile,
66    java_distribution: JavaDistribution,
67    jvm_overrides: &std::collections::HashMap<String, String>,
68    jvm_removals: &std::collections::HashSet<String>,
69    arg_overrides: &std::collections::HashMap<String, String>,
70    arg_removals: &std::collections::HashSet<String>,
71    raw_args: &[String],
72    #[cfg(feature = "events")] event_bus: Option<&EventBus>,
73) -> InstallerResult<()>
74where
75    T: VersionInfo<LoaderType = Loader> + LoaderExtensions + Arguments + Installer,
76{
77        let username = &profile.username;
78        let uuid = &profile.uuid;
79        // 1. Préparer les métadonnées du loader
80        let metadata = prepare_metadata(
81            version,
82            #[cfg(feature = "events")]
83            event_bus,
84        ).await?;
85
86        let version_data = extract_version(&metadata)?;
87
88        // 2. S'assurer que Java est installé
89        let java_path = ensure_java_installed(
90            version,
91            version_data,
92            &java_distribution,
93            #[cfg(feature = "events")]
94            event_bus,
95        ).await?;
96
97        // 3. Installer les dépendances Minecraft
98        time_it!("Install delay", version.install(
99            version_data,
100            #[cfg(feature = "events")]
101            event_bus,
102        ).await?);
103
104        // 4. Lancer le jeu
105        execute_game(version, version_data, username, uuid, java_path, arg_overrides, arg_removals, jvm_overrides, jvm_removals, raw_args).await
106}
107
108/// Récupère les métadonnées complètes du loader
109async fn prepare_metadata<T>(
110    builder: &mut T,
111    #[cfg(feature = "events")] event_bus: Option<&EventBus>,
112) -> InstallerResult<Arc<VersionMetaData>>
113where
114    T: VersionInfo<LoaderType = Loader> + LoaderExtensions,
115{
116    lighty_core::trace_debug!("[Launch] Fetching metadata for loader: {:?}", builder.loader());
117
118
119    let loader_name = format!("{:?}", builder.loader());
120
121    #[cfg(feature = "events")]
122    if let Some(bus) = event_bus {
123        bus.emit(lighty_event::Event::Loader(lighty_event::LoaderEvent::FetchingData {
124            loader: loader_name.clone(),
125            minecraft_version: builder.minecraft_version().to_string(),
126            loader_version: builder.loader_version().to_string(),
127        }));
128    }
129
130    let metadata = match builder.loader() {
131        Loader::Vanilla => builder.get_complete().await?,
132        Loader::Fabric => builder.get_fabric_complete().await?,
133        Loader::Quilt => builder.get_quilt_complete().await?,
134        Loader::NeoForge => builder.get_neoforge_complete().await?,
135        Loader::LightyUpdater => builder.get_lighty_updater_complete().await?,
136        _ => return Err(InstallerError::UnsupportedLoader(format!("{:?}", builder.loader()))),
137    };
138
139    #[cfg(feature = "events")]
140    if let Some(bus) = event_bus {
141        bus.emit(lighty_event::Event::Loader(lighty_event::LoaderEvent::DataFetched {
142            loader: loader_name,
143            minecraft_version: builder.minecraft_version().to_string(),
144            loader_version: builder.loader_version().to_string(),
145        }));
146    }
147
148    lighty_core::trace_info!("[Launch] Metadata fetched successfully for {:?}", builder.loader());
149    Ok(metadata)
150}
151
152/// S'assure que Java est installé et retourne le chemin vers l'exécutable
153async fn ensure_java_installed<T>(
154    builder: &T,
155    version: &Version,
156    java_distribution: &JavaDistribution,
157    #[cfg(feature = "events")] event_bus: Option<&EventBus>,
158) -> InstallerResult<PathBuf>
159where
160    T: VersionInfo,
161{
162    let java_version = version.java_version.major_version;
163
164    // Vérifier si Java est déjà installé
165    match find_java_binary(builder.java_dirs(), java_distribution, &java_version).await {
166        Ok(path) => {
167            lighty_core::trace_info!("[Java] Java {} already installed at: {:?}", java_version, path);
168
169            #[cfg(feature = "events")]
170            if let Some(bus) = event_bus {
171                bus.emit(lighty_event::Event::Java(lighty_event::JavaEvent::JavaAlreadyInstalled {
172                    distribution: java_distribution.get_name().to_string(),
173                    version: java_version,
174                    binary_path: path.to_string_lossy().to_string(),
175                }));
176            }
177
178            Ok(path)
179        }
180        Err(_) => {
181            lighty_core::trace_info!("[Java] Java {} not found, downloading...", java_version);
182
183            #[cfg(feature = "events")]
184            if let Some(bus) = event_bus {
185                bus.emit(lighty_event::Event::Java(lighty_event::JavaEvent::JavaNotFound {
186                    distribution: java_distribution.get_name().to_string(),
187                    version: java_version,
188                }));
189            }
190
191            #[cfg(feature = "events")]
192            let path = jre_download(
193                builder.java_dirs(),
194                java_distribution,
195                &java_version,
196                |current, total| {
197                    lighty_core::trace_debug!("[Java] Download progress: {}/{}", current, total);
198                },
199                event_bus,
200            ).await.map_err(|e| InstallerError::DownloadFailed(format!("JRE download failed: {}", e)))?;
201
202            #[cfg(not(feature = "events"))]
203            let path = jre_download(
204                builder.java_dirs(),
205                java_distribution,
206                &java_version,
207                |current, total| {
208                    lighty_core::trace_debug!("[Java] Download progress: {}/{}", current, total);
209                },
210            ).await.map_err(|e : JreError | InstallerError::DownloadFailed(format!("JRE download failed: {}", e)))?;
211
212            lighty_core::trace_info!("[Java] Java {} installed successfully", java_version);
213            Ok(path)
214        }
215    }
216}
217
218/// Lance le jeu avec les arguments appropriés
219async fn execute_game<T>(
220    builder: &T,
221    version: &Version,
222    username: &str,
223    uuid: &str,
224    java_path: PathBuf,
225    arg_overrides: &HashMap<String, String>,
226    arg_removals: &HashSet<String>,
227    jvm_overrides: &HashMap<String, String>,
228    jvm_removals: &HashSet<String>,
229    raw_args: &[String],
230) -> InstallerResult<()>
231where
232    T: VersionInfo + Arguments,
233{
234    // Construire les arguments
235    let arguments = builder.build_arguments(version, username, uuid, arg_overrides, arg_removals, jvm_overrides, jvm_removals, raw_args);
236    
237    lighty_core::trace_debug!("[Launch] Launch arguments: {:?}", arguments);
238
239    // Créer JavaRuntime avec le chemin vers java.exe
240    let java_runtime = JavaRuntime::new(java_path);
241    lighty_core::trace_info!("[Launch] Executing game...");
242
243    match java_runtime.execute(arguments, builder.game_dirs()).await {
244        Ok(mut child) => {
245            let (_tx, rx) = oneshot::channel::<()>();
246
247            if let Some(pid) = child.id() {
248                lighty_core::trace_info!("[Launch] Game launched successfully, PID: {}", pid);
249            } else {
250                lighty_core::trace_info!("[Launch] Game launched successfully, PID unavailable");
251            }
252
253            // Affiche les logs Java en temps réel dans le terminal
254            let print_output = |_: &(), buf: &[u8]| -> lighty_java::JavaRuntimeResult<()> {
255                print!("{}", String::from_utf8_lossy(buf));
256                Ok(())
257            };
258
259            if let Err(e) = java_runtime
260                .handle_io(&mut child, print_output, print_output, rx, &())
261                .await
262            {
263                lighty_core::trace_error!("[Launch] IO error: {}", e);
264            }
265
266
267
268            // tx.send(()); // <- à utiliser si tu veux forcer l'arrêt du process plus tard
269            Ok(())
270        }
271        Err(e) => {
272            lighty_core::trace_error!("[Launch] Failed to launch game: {}", e);
273            Err(InstallerError::DownloadFailed(format!("Launch failed: {}", e)))
274        }
275    }
276}
277
278/// Extrait l'objet Version depuis VersionMetaData
279fn extract_version(metadata: &VersionMetaData) -> InstallerResult<&Version> {
280    match metadata {
281        VersionMetaData::Version(v) => Ok(v),
282        _ => Err(InstallerError::InvalidMetadata),
283    }
284}