1use std::collections::{HashMap, HashSet};
2use std::path::PathBuf;
3use std::sync::Arc;
4
5use lighty_auth::UserProfile;
6use lighty_core::time_it;
7#[cfg(feature = "events")]
8use lighty_event::EventBus;
9use lighty_java::jre_downloader::{find_java_binary, jre_download};
10use lighty_java::runtime::JavaRuntime;
11use lighty_java::JavaDistribution;
12#[cfg(not(feature = "events"))]
13use lighty_java::JreError;
14use lighty_loaders::types::version_metadata::{Version, VersionMetaData};
15use lighty_loaders::types::{Loader, LoaderExtensions, VersionInfo};
16use lighty_modsloader::WithMods;
17
18use crate::arguments::Arguments;
19use crate::errors::{InstallerError, InstallerResult};
20#[cfg(any(feature = "neoforge", feature = "forge"))]
21use crate::installer::ressources::libraries::{collect_library_tasks, download_libraries};
22use crate::installer::Installer;
23
24use super::builder::LaunchBuilder;
25
26#[cfg(feature = "forge")]
27use crate::installer::processors::forge_install::run_forge_install_processors;
28#[cfg(feature = "neoforge")]
29use crate::installer::processors::forge_install::run_neoforge_install_processors;
30
31#[cfg(feature = "forge")]
32use lighty_loaders::forge::forge::{
33 extract_install_profile_libraries_modern as forge_install_profile_libraries_modern,
34 ForgeRawData, FORGE,
35};
36#[cfg(feature = "forge")]
37use lighty_loaders::forge::forge_legacy::extract_universal_jar as forge_legacy_extract_universal_jar;
38#[cfg(feature = "neoforge")]
39use lighty_loaders::neoforge::neoforge::{
40 extract_install_profile_libraries as neoforge_install_profile_libraries, NEOFORGE,
41};
42
43pub trait Launch {
48 fn launch<'a>(
73 &'a mut self,
74 profile: &'a UserProfile,
75 java_distribution: JavaDistribution,
76 ) -> LaunchBuilder<'a, Self>
77 where
78 Self: Sized;
79}
80
81impl<T> Launch for T
88where
89 T: VersionInfo<LoaderType = Loader>
90 + LoaderExtensions
91 + Arguments
92 + Installer
93 + WithMods,
94{
95 fn launch<'a>(
96 &'a mut self,
97 profile: &'a UserProfile,
98 java_distribution: JavaDistribution,
99 ) -> LaunchBuilder<'a, Self> {
100 LaunchBuilder::new(self, profile, java_distribution)
101 }
102}
103
104pub(crate) async fn execute_launch<T>(
106 version: &mut T,
107 profile: &UserProfile,
108 java_distribution: JavaDistribution,
109 jvm_overrides: &std::collections::HashMap<String, String>,
110 jvm_removals: &std::collections::HashSet<String>,
111 arg_overrides: &std::collections::HashMap<String, String>,
112 arg_removals: &std::collections::HashSet<String>,
113 raw_args: &[String],
114 #[cfg(feature = "events")] event_bus: Option<&EventBus>,
115) -> InstallerResult<()>
116where
117 T: VersionInfo<LoaderType = Loader>
118 + LoaderExtensions
119 + Arguments
120 + Installer
121 + WithMods,
122{
123 let metadata = prepare_metadata(
125 version,
126 #[cfg(feature = "events")]
127 event_bus,
128 )
129 .await?;
130
131 let version_data = extract_version(&metadata)?;
132
133 let java_path = ensure_java_installed(
135 version,
136 version_data,
137 &java_distribution,
138 #[cfg(feature = "events")]
139 event_bus,
140 )
141 .await?;
142
143 if let Some(custom) = arg_overrides.get(crate::arguments::KEY_GAME_DIRECTORY) {
150 let resolved = version.game_dirs().join(custom);
151 if resolved.as_path() != version.runtime_dir() {
152 lighty_core::trace_info!(
153 from = %version.runtime_dir().display(),
154 to = %resolved.display(),
155 source = %custom,
156 "[Launch] Resolved KEY_GAME_DIRECTORY override before install"
157 );
158 version.set_runtime_dir(resolved);
159 }
160 }
161
162 time_it!(
168 "Install delay",
169 version
170 .install(
171 version_data,
172 #[cfg(feature = "events")]
173 event_bus,
174 )
175 .await?
176 );
177
178 #[cfg(feature = "neoforge")]
190 if matches!(version.loader(), Loader::NeoForge) {
191 let install_profile = NEOFORGE.get_raw(version).await?;
192 let profile_libs = neoforge_install_profile_libraries(install_profile.as_ref());
193 let profile_tasks = collect_library_tasks(version, &profile_libs).await;
194 download_libraries(
195 profile_tasks,
196 #[cfg(feature = "events")]
197 event_bus,
198 )
199 .await?;
200 run_neoforge_install_processors(version, install_profile.as_ref(), java_path.clone())
201 .await?;
202 }
203
204 #[cfg(feature = "forge")]
205 if matches!(version.loader(), Loader::Forge) {
206 let raw = FORGE.get_raw(version).await?;
207 match raw.as_ref() {
208 ForgeRawData::Modern {
209 install_profile, ..
210 } => {
211 let profile_libs = forge_install_profile_libraries_modern(install_profile);
213 let profile_tasks = collect_library_tasks(version, &profile_libs).await;
214 download_libraries(
215 profile_tasks,
216 #[cfg(feature = "events")]
217 event_bus,
218 )
219 .await?;
220 run_forge_install_processors(version, install_profile, java_path.clone()).await?;
221 }
222 ForgeRawData::Legacy(profile) => {
223 forge_legacy_extract_universal_jar(version, profile).await?;
227 }
228 }
229 }
230
231 execute_game(
233 version,
234 version_data,
235 profile,
236 java_path,
237 arg_overrides,
238 arg_removals,
239 jvm_overrides,
240 jvm_removals,
241 raw_args,
242 #[cfg(feature = "events")]
243 event_bus,
244 )
245 .await
246}
247
248async fn prepare_metadata<T>(
250 builder: &mut T,
251 #[cfg(feature = "events")] event_bus: Option<&EventBus>,
252) -> InstallerResult<Arc<VersionMetaData>>
253where
254 T: VersionInfo<LoaderType = Loader> + LoaderExtensions,
255{
256 lighty_core::trace_debug!(
257 "[Launch] Fetching metadata for loader: {:?}",
258 builder.loader()
259 );
260
261 #[cfg(feature = "events")]
262 let loader_name = format!("{:?}", builder.loader());
263
264 #[cfg(feature = "events")]
265 if let Some(bus) = event_bus {
266 bus.emit(lighty_event::Event::Loader(
267 lighty_event::LoaderEvent::FetchingData {
268 loader: loader_name.clone(),
269 minecraft_version: builder.minecraft_version().to_string(),
270 loader_version: builder.loader_version().to_string(),
271 },
272 ));
273 }
274
275 let metadata = builder.get_metadata().await?;
277
278 #[cfg(feature = "events")]
279 if let Some(bus) = event_bus {
280 bus.emit(lighty_event::Event::Loader(
281 lighty_event::LoaderEvent::DataFetched {
282 loader: loader_name,
283 minecraft_version: builder.minecraft_version().to_string(),
284 loader_version: builder.loader_version().to_string(),
285 },
286 ));
287 }
288
289 lighty_core::trace_info!(
290 "[Launch] Metadata fetched successfully for {:?}",
291 builder.loader()
292 );
293 Ok(metadata)
294}
295
296async fn ensure_java_installed<T>(
298 builder: &T,
299 version: &Version,
300 java_distribution: &JavaDistribution,
301 #[cfg(feature = "events")] event_bus: Option<&EventBus>,
302) -> InstallerResult<PathBuf>
303where
304 T: VersionInfo,
305{
306 let java_version = version.java_version.major_version;
307
308 match find_java_binary(builder.java_dirs(), java_distribution, &java_version).await {
310 Ok(path) => {
311 lighty_core::trace_info!(
312 "[Java] Java {} already installed at: {:?}",
313 java_version,
314 path
315 );
316
317 #[cfg(feature = "events")]
318 if let Some(bus) = event_bus {
319 bus.emit(lighty_event::Event::Java(
320 lighty_event::JavaEvent::JavaAlreadyInstalled {
321 distribution: java_distribution.get_name().to_string(),
322 version: java_version,
323 binary_path: path.to_string_lossy().to_string(),
324 },
325 ));
326 }
327
328 Ok(path)
329 }
330 Err(_) => {
331 lighty_core::trace_info!("[Java] Java {} not found, downloading...", java_version);
332
333 #[cfg(feature = "events")]
334 if let Some(bus) = event_bus {
335 bus.emit(lighty_event::Event::Java(
336 lighty_event::JavaEvent::JavaNotFound {
337 distribution: java_distribution.get_name().to_string(),
338 version: java_version,
339 },
340 ));
341 }
342
343 #[cfg(feature = "events")]
344 let path = jre_download(
345 builder.java_dirs(),
346 java_distribution,
347 &java_version,
348 |current, total| {
349 lighty_core::trace_debug!("[Java] Download progress: {}/{}", current, total);
350 },
351 event_bus,
352 )
353 .await
354 .map_err(|e| InstallerError::DownloadFailed(format!("JRE download failed: {}", e)))?;
355
356 #[cfg(not(feature = "events"))]
357 let path = jre_download(
358 builder.java_dirs(),
359 java_distribution,
360 &java_version,
361 |current, total| {
362 lighty_core::trace_debug!("[Java] Download progress: {}/{}", current, total);
363 },
364 )
365 .await
366 .map_err(|e: JreError| {
367 InstallerError::DownloadFailed(format!("JRE download failed: {}", e))
368 })?;
369
370 lighty_core::trace_info!("[Java] Java {} installed successfully", java_version);
371 Ok(path)
372 }
373 }
374}
375
376async fn execute_game<T>(
378 builder: &T,
379 version: &Version,
380 profile: &UserProfile,
381 java_path: PathBuf,
382 arg_overrides: &HashMap<String, String>,
383 arg_removals: &HashSet<String>,
384 jvm_overrides: &HashMap<String, String>,
385 jvm_removals: &HashSet<String>,
386 raw_args: &[String],
387 #[cfg(feature = "events")] event_bus: Option<&EventBus>,
388) -> InstallerResult<()>
389where
390 T: VersionInfo + Arguments,
391{
392 use crate::instance::manager::GameInstance;
393 use crate::instance::{handle_console_streams, INSTANCE_MANAGER};
394
395 let username = profile.username.as_str();
396
397 let arguments = builder.build_arguments(
399 version,
400 Some(profile),
401 arg_overrides,
402 arg_removals,
403 jvm_overrides,
404 jvm_removals,
405 raw_args,
406 );
407
408 let java_runtime = JavaRuntime::new(java_path);
410 lighty_core::trace_info!("[Launch] Executing game...");
411
412 match java_runtime.execute(arguments, builder.game_dirs()).await {
413 Ok(child) => {
414 let pid = child.id().ok_or(InstallerError::NoPid)?;
415
416 lighty_core::trace_info!("[Launch] Game launched successfully, PID: {}", pid);
417
418 let instance = GameInstance {
420 pid,
421 instance_name: builder.name().to_string(),
422 version: format!(
423 "{}-{}",
424 builder.minecraft_version(),
425 builder.loader_version()
426 ),
427 username: username.to_string(),
428 game_dir: builder.game_dirs().to_path_buf(),
429 started_at: std::time::SystemTime::now(),
430 };
431
432 if let Err(e) = INSTANCE_MANAGER.register_instance(instance).await {
433 lighty_core::trace_warn!(
434 error = %e,
435 "Failed to register launched instance — process keeps running"
436 );
437 }
438
439 #[cfg(feature = "events")]
441 if let Some(bus) = event_bus {
442 use lighty_event::{Event, InstanceLaunchedEvent};
443
444 bus.emit(Event::InstanceLaunched(InstanceLaunchedEvent {
445 pid,
446 instance_name: builder.name().to_string(),
447 version: format!(
448 "{}-{}",
449 builder.minecraft_version(),
450 builder.loader_version()
451 ),
452 username: username.to_string(),
453 timestamp: std::time::SystemTime::now(),
454 }));
455
456 let bus_clone = bus.clone();
458 let instance_name = builder.name().to_string();
459 let version = format!(
460 "{}-{}",
461 builder.minecraft_version(),
462 builder.loader_version()
463 );
464 tokio::spawn(super::window::detect_window_appearance(
465 pid,
466 instance_name,
467 version,
468 bus_clone,
469 ));
470 }
471
472 tokio::spawn(handle_console_streams(
475 pid,
476 builder.name().to_string(),
477 child,
478 #[cfg(feature = "events")]
479 event_bus.cloned(),
480 ));
481
482 Ok(())
483 }
484 Err(e) => {
485 lighty_core::trace_error!("[Launch] Failed to launch game: {}", e);
486 Err(InstallerError::DownloadFailed(format!(
487 "Launch failed: {}",
488 e
489 )))
490 }
491 }
492}
493
494fn extract_version(metadata: &VersionMetaData) -> InstallerResult<&Version> {
496 match metadata {
497 VersionMetaData::Version(v) => Ok(v),
498 _ => Err(InstallerError::InvalidMetadata),
499 }
500}