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(feature = "neoforge")]
188 if matches!(version.loader(), Loader::NeoForge) {
189 let install_profile = NEOFORGE.get_raw(version).await?;
190 let profile_libs = neoforge_install_profile_libraries(install_profile.as_ref());
191 let profile_tasks = collect_library_tasks(version, &profile_libs).await;
192 download_libraries(
193 profile_tasks,
194 #[cfg(feature = "events")]
195 event_bus,
196 )
197 .await?;
198 run_neoforge_install_processors(version, install_profile.as_ref(), java_path.clone())
199 .await?;
200 }
201
202 #[cfg(feature = "forge")]
203 if matches!(version.loader(), Loader::Forge) {
204 let raw = FORGE.get_raw(version).await?;
205 match raw.as_ref() {
206 ForgeRawData::Modern {
207 install_profile, ..
208 } => {
209 let profile_libs = forge_install_profile_libraries_modern(install_profile);
211 let profile_tasks = collect_library_tasks(version, &profile_libs).await;
212 download_libraries(
213 profile_tasks,
214 #[cfg(feature = "events")]
215 event_bus,
216 )
217 .await?;
218 run_forge_install_processors(version, install_profile, java_path.clone()).await?;
219 }
220 ForgeRawData::Legacy(profile) => {
221 forge_legacy_extract_universal_jar(version, profile).await?;
225 }
226 }
227 }
228
229 execute_game(
231 version,
232 version_data,
233 profile,
234 java_path,
235 arg_overrides,
236 arg_removals,
237 jvm_overrides,
238 jvm_removals,
239 raw_args,
240 #[cfg(feature = "events")]
241 event_bus,
242 )
243 .await
244}
245
246async fn prepare_metadata<T>(
248 builder: &mut T,
249 #[cfg(feature = "events")] event_bus: Option<&EventBus>,
250) -> InstallerResult<Arc<VersionMetaData>>
251where
252 T: VersionInfo<LoaderType = Loader> + LoaderExtensions,
253{
254 lighty_core::trace_debug!(
255 "[Launch] Fetching metadata for loader: {:?}",
256 builder.loader()
257 );
258
259 #[cfg(feature = "events")]
260 let loader_name = format!("{:?}", builder.loader());
261
262 #[cfg(feature = "events")]
263 if let Some(bus) = event_bus {
264 bus.emit(lighty_event::Event::Loader(
265 lighty_event::LoaderEvent::FetchingData {
266 loader: loader_name.clone(),
267 minecraft_version: builder.minecraft_version().to_string(),
268 loader_version: builder.loader_version().to_string(),
269 },
270 ));
271 }
272
273 let metadata = builder.get_metadata().await?;
275
276 #[cfg(feature = "events")]
277 if let Some(bus) = event_bus {
278 bus.emit(lighty_event::Event::Loader(
279 lighty_event::LoaderEvent::DataFetched {
280 loader: loader_name,
281 minecraft_version: builder.minecraft_version().to_string(),
282 loader_version: builder.loader_version().to_string(),
283 },
284 ));
285 }
286
287 lighty_core::trace_info!(
288 "[Launch] Metadata fetched successfully for {:?}",
289 builder.loader()
290 );
291 Ok(metadata)
292}
293
294async fn ensure_java_installed<T>(
296 builder: &T,
297 version: &Version,
298 java_distribution: &JavaDistribution,
299 #[cfg(feature = "events")] event_bus: Option<&EventBus>,
300) -> InstallerResult<PathBuf>
301where
302 T: VersionInfo,
303{
304 let java_version = version.java_version.major_version;
305
306 match find_java_binary(builder.java_dirs(), java_distribution, &java_version).await {
308 Ok(path) => {
309 lighty_core::trace_info!(
310 "[Java] Java {} already installed at: {:?}",
311 java_version,
312 path
313 );
314
315 #[cfg(feature = "events")]
316 if let Some(bus) = event_bus {
317 bus.emit(lighty_event::Event::Java(
318 lighty_event::JavaEvent::JavaAlreadyInstalled {
319 distribution: java_distribution.get_name().to_string(),
320 version: java_version,
321 binary_path: path.to_string_lossy().to_string(),
322 },
323 ));
324 }
325
326 Ok(path)
327 }
328 Err(_) => {
329 lighty_core::trace_info!("[Java] Java {} not found, downloading...", java_version);
330
331 #[cfg(feature = "events")]
332 if let Some(bus) = event_bus {
333 bus.emit(lighty_event::Event::Java(
334 lighty_event::JavaEvent::JavaNotFound {
335 distribution: java_distribution.get_name().to_string(),
336 version: java_version,
337 },
338 ));
339 }
340
341 #[cfg(feature = "events")]
342 let path = jre_download(
343 builder.java_dirs(),
344 java_distribution,
345 &java_version,
346 |current, total| {
347 lighty_core::trace_debug!("[Java] Download progress: {}/{}", current, total);
348 },
349 event_bus,
350 )
351 .await?;
352
353 #[cfg(not(feature = "events"))]
354 let path = jre_download(
355 builder.java_dirs(),
356 java_distribution,
357 &java_version,
358 |current, total| {
359 lighty_core::trace_debug!("[Java] Download progress: {}/{}", current, total);
360 },
361 )
362 .await?;
363
364 lighty_core::trace_info!("[Java] Java {} installed successfully", java_version);
365 Ok(path)
366 }
367 }
368}
369
370async fn execute_game<T>(
372 builder: &T,
373 version: &Version,
374 profile: &UserProfile,
375 java_path: PathBuf,
376 arg_overrides: &HashMap<String, String>,
377 arg_removals: &HashSet<String>,
378 jvm_overrides: &HashMap<String, String>,
379 jvm_removals: &HashSet<String>,
380 raw_args: &[String],
381 #[cfg(feature = "events")] event_bus: Option<&EventBus>,
382) -> InstallerResult<()>
383where
384 T: VersionInfo + Arguments,
385{
386 use crate::instance::manager::GameInstance;
387 use crate::instance::{handle_console_streams, INSTANCE_MANAGER};
388
389 let username = profile.username.as_str();
390
391 let arguments = builder.build_arguments(
393 version,
394 Some(profile),
395 arg_overrides,
396 arg_removals,
397 jvm_overrides,
398 jvm_removals,
399 raw_args,
400 );
401
402 let java_runtime = JavaRuntime::new(java_path);
404 lighty_core::trace_info!("[Launch] Executing game...");
405
406 match java_runtime.execute(arguments, builder.game_dirs()).await {
407 Ok(child) => {
408 let pid = child.id().ok_or(InstallerError::NoPid)?;
409
410 lighty_core::trace_info!("[Launch] Game launched successfully, PID: {}", pid);
411
412 let instance = GameInstance {
414 pid,
415 instance_name: builder.name().to_string(),
416 version: format!(
417 "{}-{}",
418 builder.minecraft_version(),
419 builder.loader_version()
420 ),
421 username: username.to_string(),
422 game_dir: builder.game_dirs().to_path_buf(),
423 started_at: std::time::SystemTime::now(),
424 };
425
426 if let Err(e) = INSTANCE_MANAGER.register_instance(instance).await {
427 lighty_core::trace_warn!(
428 error = %e,
429 "Failed to register launched instance — process keeps running"
430 );
431 }
432
433 #[cfg(feature = "events")]
435 if let Some(bus) = event_bus {
436 use lighty_event::{Event, InstanceLaunchedEvent};
437
438 bus.emit(Event::InstanceLaunched(InstanceLaunchedEvent {
439 pid,
440 instance_name: builder.name().to_string(),
441 version: format!(
442 "{}-{}",
443 builder.minecraft_version(),
444 builder.loader_version()
445 ),
446 username: username.to_string(),
447 timestamp: std::time::SystemTime::now(),
448 }));
449
450 let bus_clone = bus.clone();
452 let instance_name = builder.name().to_string();
453 let version = format!(
454 "{}-{}",
455 builder.minecraft_version(),
456 builder.loader_version()
457 );
458 tokio::spawn(super::window::detect_window_appearance(
459 pid,
460 instance_name,
461 version,
462 bus_clone,
463 ));
464 }
465
466 tokio::spawn(handle_console_streams(
469 pid,
470 builder.name().to_string(),
471 child,
472 #[cfg(feature = "events")]
473 event_bus.cloned(),
474 ));
475
476 Ok(())
477 }
478 Err(e) => {
479 lighty_core::trace_error!("[Launch] Failed to launch game: {}", e);
480 Err(e.into())
481 }
482 }
483}
484
485fn extract_version(metadata: &VersionMetaData) -> InstallerResult<&Version> {
487 match metadata {
488 VersionMetaData::Version(v) => Ok(v),
489 _ => Err(InstallerError::InvalidMetadata),
490 }
491}