diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index e0ec370..1185b7c 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -11,6 +11,7 @@ use sha2::{Sha256, Digest}; use reqwest; use std::error::Error; use tauri::AppHandle; +use std::path::PathBuf; // Add the crawlers module mod crawlers; @@ -395,115 +396,175 @@ fn detect_server_type(server_path: &Path) -> ServerType { ServerType::Unknown } +/// Helper to find the most likely server JAR file +fn find_server_jar(server_path: &Path) -> Option { + const COMMON_NAMES: [&str; 4] = ["server.jar", "spigot.jar", "paper.jar", "craftbukkit.jar"]; + let mut largest_jar: Option<(u64, PathBuf)> = None; + let mut found_common_name: Option = None; + + if let Ok(entries) = fs::read_dir(server_path) { + for entry in entries.filter_map(Result::ok) { + let path = entry.path(); + if path.is_file() && path.extension().map_or(false, |ext| ext.eq_ignore_ascii_case("jar")) { + // Check for common names first + if let Some(filename) = path.file_name().and_then(|n| n.to_str()) { + if COMMON_NAMES.contains(&filename) { + found_common_name = Some(path.clone()); + break; // Found a primary common name, stop looking + } + } + + // Track largest JAR as a fallback + if let Ok(metadata) = entry.metadata() { + let size = metadata.len(); + if largest_jar.is_none() || size > largest_jar.as_ref().unwrap().0 { + largest_jar = Some((size, path.clone())); + } + } + } + } + } + // Prioritize common names, then largest JAR + found_common_name.or_else(|| largest_jar.map(|(_, path)| path)) +} + +/// Helper to read version.json from inside a JAR archive +fn read_version_from_jar(jar_path: &Path) -> Option { + match File::open(jar_path) { + Ok(file) => { + match ZipArchive::new(file) { + Ok(mut archive) => { + match archive.by_name("version.json") { + Ok(mut version_file) => { + let mut contents = String::new(); + if version_file.read_to_string(&mut contents).is_ok() { + if let Ok(json) = serde_json::from_str::(&contents) { + if let Some(version) = json["name"].as_str() { + return Some(version.to_string()); + } + } + } + } + Err(_) => { /* version.json not found in archive */ } + } + } + Err(e) => println!("Failed to read JAR archive {}: {}", jar_path.display(), e), + } + } + Err(e) => println!("Failed to open JAR file {}: {}", jar_path.display(), e), + } + None +} + /// Guess the Minecraft version from various files in the server directory fn detect_minecraft_version(server_path: &Path, server_type: &ServerType) -> Option { - // Try from version.json if it exists + // 1. Try external version.json if let Ok(content) = fs::read_to_string(server_path.join("version.json")) { if let Ok(json) = serde_json::from_str::(&content) { if let Some(version) = json["name"].as_str() { + println!("Version found via external version.json"); return Some(version.to_string()); } } } - // --- Try parsing paper-global.yml for Paper servers --- + // 2. Try parsing paper-global.yml for Paper servers if server_type == &ServerType::Paper { let paper_global_path = server_path.join("config").join("paper-global.yml"); if paper_global_path.exists() { if let Ok(content) = fs::read_to_string(&paper_global_path) { - // Use yaml_rust::YamlLoader here match yaml_rust::YamlLoader::load_from_str(&content) { Ok(docs) if !docs.is_empty() => { let doc = &docs[0]; - // Common location for MC version, might differ if let Some(version) = doc["misc"]["paper-version"].as_str() { - // Often includes build number, try to extract base MC version let mc_version = version.split('-').next().unwrap_or(version); + println!("Version found via paper-global.yml (misc.paper-version)"); return Some(mc_version.to_string()); } - // Fallback check, some older versions might store it differently if let Some(version) = doc["settings"]["minecraft-version"].as_str() { + println!("Version found via paper-global.yml (settings.minecraft-version)"); return Some(version.to_string()); } } - Err(e) => { - println!("Failed to parse paper-global.yml: {}", e); - } + Err(e) => println!("Failed to parse paper-global.yml: {}", e), _ => { /* Empty or invalid YAML */ } } } } } - // --- End Paper version check --- - // Try from the server jar name pattern - if let Ok(entries) = fs::read_dir(server_path) { - for entry in entries { - if let Ok(entry) = entry { - let path = entry.path(); - if path.is_file() && path.extension().map_or(false, |ext| ext == "jar") { - let filename = path.file_name().and_then(|n| n.to_str()).unwrap_or(""); - - // Extract version from various common patterns in jar names - if filename.starts_with("paper-") || - filename.starts_with("spigot-") || - filename.starts_with("craftbukkit-") { - // Pattern: paper-1.19.2.jar, spigot-1.19.2.jar - let parts: Vec<&str> = filename.split('-').collect(); - if parts.len() > 1 { - let version_part = parts[1].trim_end_matches(".jar"); - if version_part.contains('.') { // Basic version format check - return Some(version_part.to_string()); - } - } - } - - // Look for version patterns like minecraft_server.1.19.2.jar - if filename.starts_with("minecraft_server.") { - let version_part = filename - .trim_start_matches("minecraft_server.") - .trim_end_matches(".jar"); - if version_part.contains('.') { - return Some(version_part.to_string()); - } - } - } - } + // 3. Try reading version.json from inside the server JAR + if let Some(server_jar_path) = find_server_jar(server_path) { + if let Some(version) = read_version_from_jar(&server_jar_path) { + println!("Version found via internal version.json in {}", server_jar_path.display()); + return Some(version); } } - // If server type is proxy, look in config files + // 4. Try fallback: JAR filename pattern matching + if let Ok(entries) = fs::read_dir(server_path) { + for entry in entries { + if let Ok(entry) = entry { + let path = entry.path(); + if path.is_file() && path.extension().map_or(false, |ext| ext == "jar") { + let filename = path.file_name().and_then(|n| n.to_str()).unwrap_or(""); + if filename.starts_with("paper-") || filename.starts_with("spigot-") || filename.starts_with("craftbukkit-") { + let parts: Vec<&str> = filename.split('-').collect(); + if parts.len() > 1 { + let version_part = parts[1].trim_end_matches(".jar"); + if version_part.contains('.') { // Basic version format check + println!("Version inferred from JAR filename pattern: {}", filename); + return Some(version_part.to_string()); + } + } + } + if filename.starts_with("minecraft_server.") { + let version_part = filename.trim_start_matches("minecraft_server.").trim_end_matches(".jar"); + if version_part.contains('.') { + println!("Version inferred from minecraft_server JAR filename: {}", filename); + return Some(version_part.to_string()); + } + } + } + } + } + } + + // 5. Try fallback: Proxy config files (Velocity, Bungee, Waterfall) if server_type == &ServerType::BungeeCord || server_type == &ServerType::Waterfall || server_type == &ServerType::Velocity { - // Velocity uses TOML, others use YAML - if server_type == &ServerType::Velocity { - if let Ok(content) = fs::read_to_string(server_path.join("velocity.toml")) { - // Very basic TOML parsing just for this field - for line in content.lines() { - if line.contains("minecraft-version") { - if let Some(version) = line.split('=').nth(1) { - return Some(version.trim().trim_matches('"').to_string()); - } - } - } - } - } else { - // Try to parse config.yml for BungeeCord/Waterfall - if let Ok(content) = fs::read_to_string(server_path.join("config.yml")) { - if let Ok(docs) = YamlLoader::load_from_str(&content) { - if !docs.is_empty() { - let doc = &docs[0]; - if let Some(version) = doc["minecraft_version"].as_str() { - return Some(version.to_string()); - } - } - } - } - } + // ... (existing proxy config logic remains here) ... + if server_type == &ServerType::Velocity { + // ... velocity.toml check ... + if let Ok(content) = fs::read_to_string(server_path.join("velocity.toml")) { + for line in content.lines() { + if line.contains("minecraft-version") { + if let Some(version) = line.split('=').nth(1) { + println!("Version found via velocity.toml"); + return Some(version.trim().trim_matches('"').to_string()); + } + } + } + } + } else { + // ... config.yml check ... + if let Ok(content) = fs::read_to_string(server_path.join("config.yml")) { + if let Ok(docs) = YamlLoader::load_from_str(&content) { + if !docs.is_empty() { + let doc = &docs[0]; + if let Some(version) = doc["minecraft_version"].as_str() { + println!("Version found via config.yml"); + return Some(version.to_string()); + } + } + } + } + } } - // Default fallback + // 6. No version found + println!("Could not detect Minecraft version."); None } @@ -959,3 +1020,5 @@ pub fn run() { .expect("error while running tauri application"); } + +