LiquidLauncher provides a flexible system for selecting Minecraft versions through branches and builds.
Branch System
Builds are organized into branches, similar to Git branches:
Fetching Branches
src-tauri/src/app/client_api.rs
/// Request all available branches
pub async fn branches(&self) -> Result<Branches> {
self.request_from_endpoint(API_V1, "version/branches").await
}
The API returns:
src-tauri/src/app/client_api.rs
#[derive(Serialize, Deserialize)]
pub struct Branches {
#[serde(rename = "defaultBranch")]
pub default_branch: String,
pub branches: Vec<String>,
}
{
"defaultBranch": "nextgen",
"branches": ["nextgen", "legacy", "experimental"]
}
Branch Command
The frontend requests branches with retry logic:
src-tauri/src/app/gui/commands/client.rs
#[tauri::command]
pub(crate) async fn request_branches(client: Client) -> Result<Branches, String> {
let branches = (|| async { client.branches().await })
.retry(ExponentialBuilder::default())
.notify(|err, dur| {
warn!("Failed to request branches. Retrying in {:?}. Error: {}", dur, err);
})
.await
.map_err(|e| format!("unable to request branches: {:?}", e))?;
Ok(branches)
}
Exponential backoff ensures the launcher remains functional even with temporary network issues.
Build Selection
Once a branch is selected, you can fetch available builds.
Fetching Builds
Select Branch
Choose a branch from the available branches
Choose Release Filter
Decide whether to show only release builds or all builds
Request Builds
Fetch builds using the API:src-tauri/src/app/client_api.rs
pub async fn builds_by_branch(
&self,
branch: &str,
release: bool
) -> Result<Vec<Build>> {
self.request_from_endpoint(API_V1, &if release {
format!("version/builds/{}/release", branch)
} else {
format!("version/builds/{}", branch)
})
.await
}
Build Structure
Each build contains comprehensive metadata:
src-tauri/src/app/client_api.rs
#[derive(Serialize, Deserialize, Clone)]
pub struct Build {
#[serde(rename(serialize = "buildId"))]
pub build_id: u32,
#[serde(rename(serialize = "commitId"))]
pub commit_id: String,
pub branch: String,
pub subsystem: String,
#[serde(rename(serialize = "lbVersion"))]
pub lb_version: String,
#[serde(rename(serialize = "mcVersion"))]
pub mc_version: String,
pub release: bool,
pub date: DateTime<Utc>,
pub message: String,
pub url: String,
#[serde(rename(serialize = "jreDistribution"), default)]
pub jre_distribution: JavaDistribution,
#[serde(rename(serialize = "jreVersion"))]
pub jre_version: u32,
#[serde(flatten)]
pub subsystem_specific_data: SubsystemSpecificData,
}
Subsystem Data
Builds include subsystem-specific information:
src-tauri/src/app/client_api.rs
#[derive(Serialize, Deserialize, Clone)]
pub struct SubsystemSpecificData {
#[serde(rename(serialize = "fabricApiVersion"))]
pub fabric_api_version: String,
#[serde(rename(serialize = "fabricLoaderVersion"))]
pub fabric_loader_version: String,
#[serde(rename(serialize = "kotlinVersion"))]
pub kotlin_version: String,
#[serde(rename(serialize = "kotlinModVersion"))]
pub kotlin_mod_version: String,
}
This ensures compatibility with the correct Fabric API, loader, and Kotlin versions.
Requesting Builds Command
The frontend command includes retry logic and error handling:
src-tauri/src/app/gui/commands/client.rs
#[tauri::command]
pub(crate) async fn request_builds(
client: Client,
branch: &str,
release: bool
) -> Result<Vec<Build>, String> {
let builds = (|| async { client.builds_by_branch(branch, release).await })
.retry(ExponentialBuilder::default())
.notify(|err, dur| {
warn!("Failed to request builds. Retrying in {:?}. Error: {}", dur, err);
})
.await
.map_err(|e| format!("unable to request builds: {:?}", e))?;
Ok(builds)
}
Changelog
Each build has an associated changelog:
Fetching Changelog
src-tauri/src/app/client_api.rs
/// Request changelog of specified build
pub async fn fetch_changelog(&self, build_id: u32) -> Result<Changelog> {
self.request_from_endpoint(API_V1, &format!("version/changelog/{}", build_id))
.await
}
Changelog Structure
src-tauri/src/app/client_api.rs
#[derive(Serialize, Deserialize)]
pub struct Changelog {
pub build: Build,
pub changelog: String,
}
Changelog Command
src-tauri/src/app/gui/commands/client.rs
#[tauri::command]
pub(crate) async fn fetch_changelog(
client: Client,
build_id: u32
) -> Result<Changelog, String> {
(|| async { client.fetch_changelog(build_id).await })
.retry(ExponentialBuilder::default())
.notify(|err, dur| {
warn!("Failed to fetch changelog. Retrying in {:?}. Error: {}", dur, err);
})
.await
.map_err(|e| format!("unable to fetch changelog: {:?}", e))
}
Changelogs are fetched on-demand to reduce initial loading time.
Launch Manifest
Before launching, the launcher fetches a complete launch manifest:
src-tauri/src/app/client_api.rs
/// Request launch manifest of specific build
pub async fn fetch_launch_manifest(&self, build_id: u32) -> Result<LaunchManifest> {
self.request_from_endpoint(API_V1, &format!("version/launch/{}", build_id))
.await
}
Manifest Structure
src-tauri/src/app/client_api.rs
#[derive(Deserialize)]
pub struct LaunchManifest {
pub build: Build,
pub subsystem: LoaderSubsystem,
pub mods: Vec<LoaderMod>,
pub repositories: BTreeMap<String, String>,
}
- build: Complete build information
- subsystem: Fabric or Forge configuration
- mods: Required and optional mods
- repositories: Maven repositories for mod downloads
Subsystem Configuration
src-tauri/src/app/client_api.rs
#[derive(Deserialize)]
#[serde(tag = "name")]
pub enum LoaderSubsystem {
#[serde(rename = "fabric")]
Fabric {
manifest: String,
mod_directory: String,
},
#[serde(rename = "forge")]
Forge {
manifest: String,
mod_directory: String,
},
}
API Endpoints
All version-related requests go through the API client:
src-tauri/src/app/client_api.rs
pub const API_V1: &str = "api/v1";
pub async fn request_from_endpoint<T: DeserializeOwned>(
&self,
api_version: &str,
endpoint: &str
) -> Result<T> {
Ok(HTTP_CLIENT
.get(format!("{}/{}/{}", self.url, api_version, endpoint))
.header("X-Session-Token", &self.session_token)
.send()
.await?
.error_for_status()?
.json::<T>()
.await?)
}
All requests include a session token for authentication and rate limiting.
Usage Flow
Fetch Branches
Get list of available branches and the default branch
Select Branch
User chooses a branch (or uses default)
Fetch Builds
Load all builds for the selected branch
Filter Builds
Optionally filter to show only release builds
View Changelog
User can view changelog for any build
Select Build
User chooses a specific build to launch
Fetch Launch Manifest
Launcher retrieves complete launch configuration
Launch
Game is launched with the selected build
Blog Posts
The API also provides blog posts for news and updates:
src-tauri/src/app/client_api.rs
/// Request all blog posts
pub async fn blog_posts(&self, page: u32) -> Result<PaginatedResponse<BlogPost>> {
self.request_from_endpoint(API_V3, &format!("blog?page={}", page)).await
}
#[derive(Serialize, Deserialize)]
pub struct BlogPost {
#[serde(rename(serialize = "postId"))]
pub post_id: u32,
#[serde(rename(serialize = "postUid"))]
pub post_uid: String,
pub author: String,
pub title: String,
pub description: String,
pub date: NaiveDateTime,
#[serde(rename(serialize = "bannerText"))]
pub banner_text: String,
#[serde(rename(serialize = "bannerImageUrl"))]
pub banner_image_url: String,
}
Auto-Updates
Builds are automatically updated from the API
Java Management
Each build specifies required Java version
Custom Mods
Install additional mods for any build