aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/src-tauri/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src-tauri/src/main.rs')
-rw-r--r--src-tauri/src/main.rs360
1 files changed, 210 insertions, 150 deletions
diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs
index 91b61ed..02a2b18 100644
--- a/src-tauri/src/main.rs
+++ b/src-tauri/src/main.rs
@@ -1,9 +1,9 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
-use tauri::{Manager, State, Window, Emitter}; // Added Emitter
use std::process::Stdio;
-use tokio::io::{BufReader, AsyncBufReadExt};
+use tauri::{Emitter, Manager, State, Window}; // Added Emitter
+use tokio::io::{AsyncBufReadExt, BufReader};
use tokio::process::Command;
mod core;
@@ -15,40 +15,56 @@ async fn start_game(
window: Window,
auth_state: State<'_, core::auth::AccountState>,
config_state: State<'_, core::config::ConfigState>,
- version_id: String
+ version_id: String,
) -> Result<String, String> {
println!("Backend received StartGame for {}", version_id);
-
+
// Check for active account
- let account = auth_state.active_account.lock().unwrap().clone()
+ let account = auth_state
+ .active_account
+ .lock()
+ .unwrap()
+ .clone()
.ok_or("No active account found. Please login first.")?;
-
+
let config = config_state.config.lock().unwrap().clone();
// Get App Data Directory (e.g., ~/.local/share/com.dropout.launcher or similar)
// The identifier is set in tauri.conf.json.
// If not accessible, use a specific logic.
let app_handle = window.app_handle();
- let game_dir = app_handle.path().app_data_dir()
+ let game_dir = app_handle
+ .path()
+ .app_data_dir()
.map_err(|e| format!("Failed to get app data dir: {}", e))?;
-
+
// Ensure game directory exists
- tokio::fs::create_dir_all(&game_dir).await.map_err(|e| e.to_string())?;
+ tokio::fs::create_dir_all(&game_dir)
+ .await
+ .map_err(|e| e.to_string())?;
println!("Game Directory: {:?}", game_dir);
// 1. Fetch manifest to find the version URL
- let manifest = core::manifest::fetch_version_manifest().await.map_err(|e| e.to_string())?;
-
+ let manifest = core::manifest::fetch_version_manifest()
+ .await
+ .map_err(|e| e.to_string())?;
+
// Find the version info
- let version_info = manifest.versions.iter().find(|v| v.id == version_id)
+ let version_info = manifest
+ .versions
+ .iter()
+ .find(|v| v.id == version_id)
.ok_or_else(|| format!("Version {} not found in manifest", version_id))?;
-
+
// 2. Fetch specific version JSON (client.jar info)
let version_url = &version_info.url;
let version_details: core::game_version::GameVersion = reqwest::get(version_url)
- .await.map_err(|e| e.to_string())?
- .json().await.map_err(|e| e.to_string())?;
+ .await
+ .map_err(|e| e.to_string())?
+ .json()
+ .await
+ .map_err(|e| e.to_string())?;
// 3. Prepare download tasks
let mut download_tasks = Vec::new();
@@ -69,59 +85,64 @@ async fn start_game(
println!("Processing libraries...");
let libraries_dir = game_dir.join("libraries");
let mut native_libs_paths = Vec::new(); // Store paths to native jars for extraction
-
+
for lib in &version_details.libraries {
if core::rules::is_library_allowed(&lib.rules) {
- // 1. Standard Library
- if let Some(downloads) = &lib.downloads {
- if let Some(artifact) = &downloads.artifact {
- let path_str = artifact.path.clone().unwrap_or_else(|| {
- format!("{}.jar", lib.name)
- });
-
- let mut lib_path = libraries_dir.clone();
- lib_path.push(path_str);
-
- download_tasks.push(core::downloader::DownloadTask {
- url: artifact.url.clone(),
- path: lib_path,
- sha1: Some(artifact.sha1.clone()),
- });
- }
-
- // 2. Native Library (classifiers)
- // e.g. "natives-linux": { ... }
- if let Some(classifiers) = &downloads.classifiers {
- // Determine the key based on OS
- // Linux usually "natives-linux", Windows "natives-windows", Mac "natives-osx" (or macos)
- let os_key = if cfg!(target_os = "linux") {
- "natives-linux"
- } else if cfg!(target_os = "windows") {
- "natives-windows"
- } else if cfg!(target_os = "macos") {
- "natives-osx" // or natives-macos? check json
- } else {
- ""
- };
-
- if let Some(native_artifact_value) = classifiers.get(os_key) {
- // Parse it as DownloadArtifact
- if let Ok(native_artifact) = serde_json::from_value::<core::game_version::DownloadArtifact>(native_artifact_value.clone()) {
- let path_str = native_artifact.path.clone().unwrap(); // Natives usually have path
- let mut native_path = libraries_dir.clone();
- native_path.push(&path_str);
-
- download_tasks.push(core::downloader::DownloadTask {
- url: native_artifact.url,
- path: native_path.clone(),
- sha1: Some(native_artifact.sha1),
- });
-
- native_libs_paths.push(native_path);
- }
- }
- }
- }
+ // 1. Standard Library
+ if let Some(downloads) = &lib.downloads {
+ if let Some(artifact) = &downloads.artifact {
+ let path_str = artifact
+ .path
+ .clone()
+ .unwrap_or_else(|| format!("{}.jar", lib.name));
+
+ let mut lib_path = libraries_dir.clone();
+ lib_path.push(path_str);
+
+ download_tasks.push(core::downloader::DownloadTask {
+ url: artifact.url.clone(),
+ path: lib_path,
+ sha1: Some(artifact.sha1.clone()),
+ });
+ }
+
+ // 2. Native Library (classifiers)
+ // e.g. "natives-linux": { ... }
+ if let Some(classifiers) = &downloads.classifiers {
+ // Determine the key based on OS
+ // Linux usually "natives-linux", Windows "natives-windows", Mac "natives-osx" (or macos)
+ let os_key = if cfg!(target_os = "linux") {
+ "natives-linux"
+ } else if cfg!(target_os = "windows") {
+ "natives-windows"
+ } else if cfg!(target_os = "macos") {
+ "natives-osx" // or natives-macos? check json
+ } else {
+ ""
+ };
+
+ if let Some(native_artifact_value) = classifiers.get(os_key) {
+ // Parse it as DownloadArtifact
+ if let Ok(native_artifact) =
+ serde_json::from_value::<core::game_version::DownloadArtifact>(
+ native_artifact_value.clone(),
+ )
+ {
+ let path_str = native_artifact.path.clone().unwrap(); // Natives usually have path
+ let mut native_path = libraries_dir.clone();
+ native_path.push(&path_str);
+
+ download_tasks.push(core::downloader::DownloadTask {
+ url: native_artifact.url,
+ path: native_path.clone(),
+ sha1: Some(native_artifact.sha1),
+ });
+
+ native_libs_paths.push(native_path);
+ }
+ }
+ }
+ }
}
}
@@ -133,23 +154,35 @@ async fn start_game(
// Download Asset Index JSON
let asset_index_path = indexes_dir.join(format!("{}.json", version_details.asset_index.id));
-
+
// Check if index exists or download it
- // Note: We need the content of this file to parse it.
- // If we just add it to download_tasks, we can't parse it *now*.
+ // Note: We need the content of this file to parse it.
+ // If we just add it to download_tasks, we can't parse it *now*.
// So we must download it immediately (await) before processing objects.
let asset_index_content: String = if asset_index_path.exists() {
- tokio::fs::read_to_string(&asset_index_path).await.map_err(|e| e.to_string())?
+ tokio::fs::read_to_string(&asset_index_path)
+ .await
+ .map_err(|e| e.to_string())?
} else {
- println!("Downloading asset index from {}", version_details.asset_index.url);
+ println!(
+ "Downloading asset index from {}",
+ version_details.asset_index.url
+ );
let content = reqwest::get(&version_details.asset_index.url)
- .await.map_err(|e| e.to_string())?
- .text().await.map_err(|e| e.to_string())?;
-
+ .await
+ .map_err(|e| e.to_string())?
+ .text()
+ .await
+ .map_err(|e| e.to_string())?;
+
// Save it for next time
- tokio::fs::create_dir_all(&indexes_dir).await.map_err(|e| e.to_string())?;
- tokio::fs::write(&asset_index_path, &content).await.map_err(|e| e.to_string())?;
+ tokio::fs::create_dir_all(&indexes_dir)
+ .await
+ .map_err(|e| e.to_string())?;
+ tokio::fs::write(&asset_index_path, &content)
+ .await
+ .map_err(|e| e.to_string())?;
content
};
@@ -158,22 +191,26 @@ async fn start_game(
hash: String,
size: u64,
}
-
+
#[derive(serde::Deserialize, Debug)]
struct AssetIndexJson {
objects: std::collections::HashMap<String, AssetObject>,
}
- let asset_index_parsed: AssetIndexJson = serde_json::from_str(&asset_index_content).map_err(|e| e.to_string())?;
-
+ let asset_index_parsed: AssetIndexJson =
+ serde_json::from_str(&asset_index_content).map_err(|e| e.to_string())?;
+
println!("Processing {} assets...", asset_index_parsed.objects.len());
-
+
for (_name, object) in asset_index_parsed.objects {
let hash = object.hash;
let prefix = &hash[0..2];
let path = objects_dir.join(prefix).join(&hash);
- let url = format!("https://resources.download.minecraft.net/{}/{}", prefix, hash);
-
+ let url = format!(
+ "https://resources.download.minecraft.net/{}/{}",
+ prefix, hash
+ );
+
download_tasks.push(core::downloader::DownloadTask {
url,
path,
@@ -181,61 +218,74 @@ async fn start_game(
});
}
+ println!(
+ "Total download tasks (Client + Libs + Assets): {}",
+ download_tasks.len()
+ );
- println!("Total download tasks (Client + Libs + Assets): {}", download_tasks.len());
-
// 4. Start Download
- core::downloader::download_files(window.clone(), download_tasks).await.map_err(|e| e.to_string())?;
+ core::downloader::download_files(window.clone(), download_tasks)
+ .await
+ .map_err(|e| e.to_string())?;
// 5. Extract Natives
println!("Extracting natives...");
let natives_dir = game_dir.join("versions").join(&version_id).join("natives");
-
+
// Clean old natives if they exist to prevent conflicts
if natives_dir.exists() {
- tokio::fs::remove_dir_all(&natives_dir).await.map_err(|e| e.to_string())?;
+ tokio::fs::remove_dir_all(&natives_dir)
+ .await
+ .map_err(|e| e.to_string())?;
}
- tokio::fs::create_dir_all(&natives_dir).await.map_err(|e| e.to_string())?;
+ tokio::fs::create_dir_all(&natives_dir)
+ .await
+ .map_err(|e| e.to_string())?;
for path in native_libs_paths {
if path.exists() {
- println!("Extracting native: {:?}", path);
- utils::zip::extract_zip(&path, &natives_dir)?;
+ println!("Extracting native: {:?}", path);
+ utils::zip::extract_zip(&path, &natives_dir)?;
}
}
// 6. Construct Classpath
- let cp_separator = if cfg!(target_os = "windows") { ";" } else { ":" };
+ let cp_separator = if cfg!(target_os = "windows") {
+ ";"
+ } else {
+ ":"
+ };
let mut classpath_entries = Vec::new();
-
+
// Add libraries
for lib in &version_details.libraries {
if core::rules::is_library_allowed(&lib.rules) {
- if let Some(downloads) = &lib.downloads {
- if let Some(artifact) = &downloads.artifact {
- let path_str = artifact.path.clone().unwrap_or_else(|| {
- format!("{}.jar", lib.name)
- });
- let lib_path = libraries_dir.join(path_str);
- classpath_entries.push(lib_path.to_string_lossy().to_string());
- }
- }
+ if let Some(downloads) = &lib.downloads {
+ if let Some(artifact) = &downloads.artifact {
+ let path_str = artifact
+ .path
+ .clone()
+ .unwrap_or_else(|| format!("{}.jar", lib.name));
+ let lib_path = libraries_dir.join(path_str);
+ classpath_entries.push(lib_path.to_string_lossy().to_string());
+ }
+ }
}
}
// Add client jar
classpath_entries.push(client_path.to_string_lossy().to_string());
-
+
let classpath = classpath_entries.join(cp_separator);
// 7. Prepare Arguments
let mut args = Vec::new();
let natives_path = natives_dir.to_string_lossy().to_string();
-
+
// 7a. JVM Arguments (Simplified for now)
// We inject standard convenient defaults.
// TODO: Parse 'arguments.jvm' from version.json for full compatibility (Mac M1 support etc)
args.push(format!("-Djava.library.path={}", natives_path));
- args.push(format!("-Xmx{}M", config.max_memory));
+ args.push(format!("-Xmx{}M", config.max_memory));
args.push(format!("-Xms{}M", config.min_memory));
args.push("-cp".to_string());
args.push(classpath);
@@ -250,7 +300,10 @@ async fn start_game(
replacements.insert("${version_name}", version_id.clone());
replacements.insert("${game_directory}", game_dir.to_string_lossy().to_string());
replacements.insert("${assets_root}", assets_dir.to_string_lossy().to_string());
- replacements.insert("${assets_index_name}", version_details.asset_index.id.clone());
+ replacements.insert(
+ "${assets_index_name}",
+ version_details.asset_index.id.clone(),
+ );
replacements.insert("${auth_uuid}", account.uuid.clone());
replacements.insert("${auth_access_token}", "null".to_string()); // Offline
replacements.insert("${user_type}", "mojang".to_string());
@@ -269,31 +322,33 @@ async fn start_game(
} else if let Some(args_obj) = &version_details.arguments {
if let Some(game_args) = &args_obj.game {
// Can be array of strings or objects
- if let Some(list) = game_args.as_array() {
- for item in list {
- if let Some(s) = item.as_str() {
- let mut arg = s.to_string();
- for (key, val) in &replacements {
- arg = arg.replace(key, val);
- }
- args.push(arg);
- } else if let Some(obj) = item.as_object() {
- // Check rules
- // Simplified: if it has "value", and rules pass.
- // For now, assuming rules pass if no "rules" field or simplistic check
- // Ideally we should implement a helper to check rules for args just like libs
-
- let allow = if let Some(rules_val) = obj.get("rules") {
- if let Ok(rules) = serde_json::from_value::<Vec<core::game_version::Rule>>(rules_val.clone()) {
- core::rules::is_library_allowed(&Some(rules))
- } else {
- true // Parse error, assume allow? or disallow.
- }
- } else {
- true
- };
-
- if allow {
+ if let Some(list) = game_args.as_array() {
+ for item in list {
+ if let Some(s) = item.as_str() {
+ let mut arg = s.to_string();
+ for (key, val) in &replacements {
+ arg = arg.replace(key, val);
+ }
+ args.push(arg);
+ } else if let Some(obj) = item.as_object() {
+ // Check rules
+ // Simplified: if it has "value", and rules pass.
+ // For now, assuming rules pass if no "rules" field or simplistic check
+ // Ideally we should implement a helper to check rules for args just like libs
+
+ let allow = if let Some(rules_val) = obj.get("rules") {
+ if let Ok(rules) = serde_json::from_value::<Vec<core::game_version::Rule>>(
+ rules_val.clone(),
+ ) {
+ core::rules::is_library_allowed(&Some(rules))
+ } else {
+ true // Parse error, assume allow? or disallow.
+ }
+ } else {
+ true
+ };
+
+ if allow {
if let Some(val) = obj.get("value") {
if let Some(s) = val.as_str() {
let mut arg = s.to_string();
@@ -313,17 +368,17 @@ async fn start_game(
}
}
}
- }
- }
- }
- }
+ }
+ }
+ }
+ }
}
}
println!("Launching game with {} args...", args.len());
// Debug: Print arguments to help diagnose issues
println!("Launch Args: {:?}", args);
-
+
// Spawn the process
let mut command = Command::new(&config.java_path);
command.args(&args);
@@ -332,16 +387,24 @@ async fn start_game(
command.stderr(Stdio::piped());
// Spawn and handle output
- let mut child = command.spawn().map_err(|e| format!("Failed to launch java: {}", e))?;
-
- let stdout = child.stdout.take().expect("child did not have a handle to stdout");
- let stderr = child.stderr.take().expect("child did not have a handle to stderr");
+ let mut child = command
+ .spawn()
+ .map_err(|e| format!("Failed to launch java: {}", e))?;
+
+ let stdout = child
+ .stdout
+ .take()
+ .expect("child did not have a handle to stdout");
+ let stderr = child
+ .stderr
+ .take()
+ .expect("child did not have a handle to stderr");
let window_rx = window.clone();
tokio::spawn(async move {
let mut reader = BufReader::new(stdout).lines();
while let Ok(Some(line)) = reader.next_line().await {
- let _ = window_rx.emit("game-stdout", line);
+ let _ = window_rx.emit("game-stdout", line);
}
});
@@ -349,7 +412,7 @@ async fn start_game(
tokio::spawn(async move {
let mut reader = BufReader::new(stderr).lines();
while let Ok(Some(line)) = reader.next_line().await {
- let _ = window_rx_err.emit("game-stderr", line);
+ let _ = window_rx_err.emit("game-stderr", line);
}
});
@@ -370,11 +433,8 @@ async fn login_offline(
username: String,
) -> Result<core::auth::OfflineAccount, String> {
let uuid = core::auth::generate_offline_uuid(&username);
- let account = core::auth::OfflineAccount {
- username,
- uuid,
- };
-
+ let account = core::auth::OfflineAccount { username, uuid };
+
*state.active_account.lock().unwrap() = Some(account.clone());
Ok(account)
}
@@ -419,10 +479,10 @@ fn main() {
Ok(())
})
.invoke_handler(tauri::generate_handler![
- start_game,
- get_versions,
- login_offline,
- get_active_account,
+ start_game,
+ get_versions,
+ login_offline,
+ get_active_account,
logout,
get_settings,
save_settings