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