The launcher core is responsible for preparing and launching the Minecraft client. This involves downloading assets, libraries, and mods, setting up the Java runtime, and constructing the launch command.
Launch Flow Overview
Prelauncher
The prelauncher (src-tauri/src/minecraft/prelauncher.rs) handles pre-launch setup:
Version Profile Loading
Version profiles define how to launch a specific Minecraft version:
1. Fetch Version Manifest
Load the Minecraft version manifest from Mojang:
let mc_version_manifest = VersionManifest :: fetch
. retry ( ExponentialBuilder :: default ())
. notify ( | err , dur | {
launcher_data . log ( & format! (
"Failed to load version manifest. Retrying in {:?}. Error: {}" ,
dur , err
));
})
. await ? ;
Construct the loader-specific manifest URL (Fabric or Forge):
let manifest_url = match subsystem {
LoaderSubsystem :: Fabric { manifest , .. } => manifest
. replace ( "{MINECRAFT_VERSION}" , & build . mc_version)
. replace (
"{FABRIC_LOADER_VERSION}" ,
& build . subsystem_specific_data . fabric_loader_version,
),
LoaderSubsystem :: Forge { manifest , .. } => manifest . clone (),
};
let mut version = ( || async { VersionProfile :: load ( & manifest_url ) . await })
. retry ( ExponentialBuilder :: default ())
. await ? ;
3. Merge Inherited Profiles
Many loaders inherit from base Minecraft versions:
if let Some ( inherited_version ) = & version . inherits_from {
let url = mc_version_manifest
. versions
. iter ()
. find ( | x | & x . id == inherited_version )
. map ( | x | & x . url)
. ok_or_else ( || {
LauncherError :: InvalidVersionProfile ( format! (
"unable to find inherited version manifest {}" ,
inherited_version
))
}) ? ;
let parent_version = ( || async { VersionProfile :: load ( url ) . await })
. retry ( ExponentialBuilder :: default ())
. await ? ;
version . merge ( parent_version ) ? ;
}
Version Profile Structure
Version profiles are JSON files that describe:
src-tauri/src/minecraft/version.rs:81-101
pub struct VersionProfile {
pub id : String ,
pub asset_index_location : Option < AssetIndexLocation >,
pub assets : Option < String >,
pub inherits_from : Option < String >,
pub minimum_launcher_version : Option < i32 >,
pub downloads : Option < Downloads >,
pub compliance_level : Option < i32 >,
pub libraries : Vec < Library >,
pub main_class : Option < String >,
pub logging : Option < Logging >,
pub version_type : String ,
pub arguments : ArgumentDeclaration ,
}
Version profiles can inherit from other profiles. For example, Fabric 1.20.1 inherits from vanilla Minecraft 1.20.1.
Mod Management
The prelauncher handles mod downloading and installation:
src-tauri/src/minecraft/prelauncher.rs:84-102
// Clear old mods
clear_mods ( & data_directory , & launch_manifest ) . await ? ;
// Download and copy manifest mods
retrieve_and_copy_mods (
& data_directory ,
& launch_manifest ,
& launch_manifest . mods,
client ,
retriever_account ,
& launcher_data ,
) . await ? ;
// Download and copy additional mods
retrieve_and_copy_mods (
& data_directory ,
& launch_manifest ,
& additional_mods ,
client ,
retriever_account ,
& launcher_data ,
) . await ? ;
Mods can come from multiple sources:
Repository : Maven-style artifact download
SkipAd : Ad-supported download with premium bypass
Local : Custom user-installed mods
Launcher Core
The launcher (src-tauri/src/minecraft/launcher/mod.rs) executes the launch process:
Launch Parameters
All launch configuration is passed via StartParameter:
src-tauri/src/minecraft/launcher/mod.rs:263-279
pub struct StartParameter {
pub java_distribution : DistributionSelection ,
pub jvm_args : Vec < String >,
pub memory : u64 ,
pub custom_data_path : Option < String >,
pub auth_player_name : String ,
pub auth_uuid : String ,
pub auth_access_token : String ,
pub auth_xuid : String ,
pub clientid : String ,
pub user_type : String ,
pub keep_launcher_open : bool ,
pub concurrent_downloads : u32 ,
pub client : Client ,
pub client_account : Option < ClientAccount >,
pub skip_advertisement : bool ,
}
Directory Setup
The launcher creates necessary directories:
src-tauri/src/minecraft/launcher/mod.rs:99-104
let runtimes_folder = join_and_mkdir! ( data , "runtimes" );
let client_folder = join_and_mkdir_vec! ( data , vec! [ "versions" , & version_profile . id]);
let natives_folder = join_and_mkdir! ( client_folder , "natives" );
let libraries_folder = join_and_mkdir! ( data , "libraries" );
let assets_folder = join_and_mkdir! ( data , "assets" );
let game_dir = join_and_mkdir_vec! ( data , vec! [ "gameDir" , &* manifest . build . branch]);
Java Runtime Setup
The launcher ensures the correct Java version is available:
src-tauri/src/minecraft/launcher/mod.rs:106-118
let java_bin = load_jre (
& runtimes_folder ,
& manifest ,
& launching_parameter ,
& launcher_data ,
)
. await
. context ( "Failed to load JRE" ) ? ;
launcher_data . log ( & format! ( "Java Path: {:?}" , java_bin ));
if ! java_bin . exists () {
bail! ( "Java binary not found" );
}
The JRE loader (src-tauri/src/minecraft/launcher/jre.rs) handles:
Distribution Selection : Checks which Java distribution to use (Adoptium, Azul, etc.)
Version Detection : Determines required Java version from manifest
Download : Downloads JRE if not present
Extraction : Extracts JRE archive
Path Resolution : Returns path to java/javaw binary
Client JAR Setup
src-tauri/src/minecraft/launcher/mod.rs:120-129
setup_client_jar (
& client_folder ,
& natives_folder ,
& version_profile ,
& launcher_data ,
& mut class_path ,
)
. await
. context ( "Failed to setup client JAR" ) ? ;
This downloads the Minecraft client JAR and adds it to the classpath.
Library Setup
src-tauri/src/minecraft/launcher/mod.rs:131-142
setup_libraries (
& libraries_folder ,
& natives_folder ,
& version_profile ,
& launching_parameter ,
& launcher_data ,
& features ,
& mut class_path ,
)
. await
. context ( "Failed to setup libraries" ) ? ;
Libraries are downloaded from Maven repositories: src-tauri/src/minecraft/version.rs:640-645
pub async fn download (
& self ,
name : & str ,
libraries_folder : PathBuf ,
progress : & impl ProgressReceiver ,
) -> Result < PathBuf >
The process:
Check if library exists and SHA1 matches
Download if missing or checksum mismatch
Verify downloaded file’s checksum
Extract natives (platform-specific DLLs/SOs)
Add to classpath
Asset Setup
src-tauri/src/minecraft/launcher/mod.rs:144-152
let asset_index_location = setup_assets (
& assets_folder ,
& version_profile ,
& launching_parameter ,
& launcher_data ,
)
. await
. context ( "Failed to setup assets" ) ? ;
Assets include sounds, textures, and language files.
Command Construction
The launcher builds the Java command with all necessary arguments:
JVM Arguments
src-tauri/src/minecraft/launcher/mod.rs:160-178
// JVM Args
version_profile . arguments . add_jvm_args_to_vec (
& mut command_arguments ,
& launching_parameter ,
& features ,
) ? ;
// Launcher Args (-D<name>=<value>)
command_arguments . push ( format! (
"-Dnet.ccbluex.liquidbounce.api.url={}" ,
launching_parameter . client . url ()
));
command_arguments . push ( format! (
"-Dnet.ccbluex.liquidbounce.api.secure={}" ,
launching_parameter . client . is_secure ()
));
command_arguments . push ( format! (
"-Dnet.ccbluex.liquidbounce.api.token={}" ,
launching_parameter . client . session_token ()
));
Default JVM arguments from src-tauri/src/minecraft/version.rs:232-238:
command_arguments . push ( format! ( "-Xmx{}M" , parameter . memory));
command_arguments . push ( "-XX:+UnlockExperimentalVMOptions" . to_string ());
command_arguments . push ( "-XX:+UseG1GC" . to_string ());
command_arguments . push ( "-XX:G1NewSizePercent=20" . to_string ());
command_arguments . push ( "-XX:G1ReservePercent=20" . to_string ());
command_arguments . push ( "-XX:MaxGCPauseMillis=50" . to_string ());
command_arguments . push ( "-XX:G1HeapRegionSize=32M" . to_string ());
Main Class
src-tauri/src/minecraft/launcher/mod.rs:180-189
command_arguments . push (
version_profile
. main_class
. as_ref ()
. ok_or_else ( || {
LauncherError :: InvalidVersionProfile ( "Main class unspecified" . to_string ())
}) ?
. to_owned (),
);
Game Arguments
src-tauri/src/minecraft/launcher/mod.rs:191-194
version_profile
. arguments
. add_game_args_to_vec ( & mut command_arguments , & features ) ? ;
Template Processing
Arguments contain templates like ${game_directory} that must be replaced:
src-tauri/src/minecraft/launcher/mod.rs:198-228
for x in command_arguments . iter () {
mapped . push ( process_templates ( x , | output , param | {
match param {
"auth_player_name" => output . push_str ( & launching_parameter . auth_player_name),
"version_name" => output . push_str ( & version_profile . id),
"game_directory" => {
output . push_str ( game_dir . absolutize () . unwrap () . to_str () . unwrap ())
}
"assets_root" => {
output . push_str ( assets_folder . absolutize () . unwrap () . to_str () . unwrap ())
}
"assets_index_name" => output . push_str ( & asset_index_location . id),
"auth_uuid" => output . push_str ( & launching_parameter . auth_uuid),
"auth_access_token" => output . push_str ( & launching_parameter . auth_access_token),
"user_type" => output . push_str ( & launching_parameter . user_type),
"version_type" => output . push_str ( & version_profile . version_type),
"natives_directory" => {
output . push_str ( natives_folder . absolutize () . unwrap () . to_str () . unwrap ())
}
"launcher_name" => output . push_str ( "LiquidLauncher" ),
"launcher_version" => output . push_str ( LAUNCHER_VERSION ),
"classpath" => output . push_str ( & class_path ),
"user_properties" => output . push_str ( "{}" ),
"clientid" => output . push_str ( & launching_parameter . clientid),
"auth_xuid" => output . push_str ( & launching_parameter . auth_xuid),
_ => return Err ( LauncherError :: UnknownTemplateParameter ( param . to_owned ()) . into ()),
};
Ok (())
}) ? )
}
Process Execution
The final step is spawning the Java process:
Spawning the Process
src-tauri/src/minecraft/launcher/mod.rs:233-235
let mut running_task = java_runtime . execute ( mapped , & game_dir ) . await ? ;
The JavaRuntime wrapper (src-tauri/src/minecraft/java/runtime.rs):
src-tauri/src/minecraft/java/runtime.rs:34-48
pub async fn execute ( & self , arguments : Vec < String >, game_dir : & Path ) -> Result < Child > {
if ! self . 0. exists () {
bail! ( "Java runtime not found at: {}" , self . 0. display ());
}
debug! ( "Executing Java runtime: {}" , self . 0. display ());
let mut command = Command :: new ( & self . 0 );
command . current_dir ( game_dir );
command . args ( arguments );
command . stderr ( Stdio :: piped ()) . stdout ( Stdio :: piped ());
let child = command . spawn () ? ;
Ok ( child )
}
I/O Handling
The launcher streams stdout/stderr to the UI:
src-tauri/src/minecraft/java/runtime.rs:67-88
loop {
tokio :: select! {
read_len = stdout . read ( & mut stdout_buf ) => {
let _ = on_stdout ( & data , & stdout_buf [ .. read_len ? ]);
},
read_len = stderr . read ( & mut stderr_buf ) => {
let _ = on_stderr ( & data , & stderr_buf [ .. read_len ? ]);
},
_ = & mut terminator => {
running_task . kill () . await ? ;
break ;
},
exit_status = running_task . wait () => {
let code = exit_status ?. code () . unwrap_or ( 7900 );
debug! ( "Process exited with code: {}" , code );
if code != 0 && code != - 1073740791 {
bail! ( "Process exited with non-zero exit code: {}." , code );
}
break ;
},
}
}
The tokio::select! macro allows concurrent handling of multiple async operations: reading stdout, reading stderr, waiting for termination signal, and waiting for process exit.
Window Management
src-tauri/src/minecraft/launcher/mod.rs:237-240
if ! launching_parameter . keep_launcher_open {
// Hide launcher window
launcher_data . hide_window ();
}
After launch, the launcher can either:
Hide and restore on game exit
Stay visible to show logs
Progress Tracking
The launcher reports progress through the ProgressReceiver trait:
pub trait ProgressReceiver {
fn progress_update ( & self , progress_update : ProgressUpdate );
fn log ( & self , msg : & str );
}
Progress updates include:
SetLabel : Status message (“Downloading assets…”)
SetProgress : Numeric progress (0-100)
SetToMax : Set progress to maximum
SetForStep : Progress for a specific step
Error Handling
The launch process uses comprehensive error handling:
launcher :: launch (
& data_directory ,
launch_manifest ,
version ,
launching_parameter ,
launcher_data ,
)
. await
. context ( "Launch failed" ) ? ;
Each stage can fail and propagate errors:
Network failures (manifest/library download)
File system errors (permissions, disk space)
Process spawn failures (Java not found)
Runtime errors (incompatible Java version)
Launch Example
A complete launch command might look like:
/path/to/java \
-Xmx4096M \
-XX:+UseG1GC \
-Djava.library.path=/path/to/natives \
-Dnet.ccbluex.liquidbounce.api.url=https://api.liquidbounce.net \
-cp /path/to/libs/ * .jar:/path/to/client.jar \
net.minecraft.client.main.Main \
--username Player \
--version 1.20.1 \
--gameDir /path/to/gameDir \
--assetsDir /path/to/assets \
--assetIndex 1.20 \
--uuid 12345678-1234-1234-1234-123456789012 \
--accessToken < toke n > \
--userType msa
Best Practices
Always verify checksums - Libraries and assets should be verified against SHA1 hashes
Use retry logic - Network operations should retry with exponential backoff
Report progress - Keep the user informed during long operations
Handle process lifecycle - Properly manage process termination and cleanup
Merge version profiles correctly - Respect inheritance hierarchy when merging profiles
Next Steps
Backend Architecture Learn about Rust modules and command structure
Frontend Architecture Explore Svelte components and UI