feat: Enhance Minecraft version detection with JAR file analysis and improved fallback mechanisms

This commit is contained in:
Rbanh 2025-03-29 01:42:58 -04:00
parent fc96a10397
commit 4adb291592

View File

@ -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,75 +396,132 @@ fn detect_server_type(server_path: &Path) -> ServerType {
ServerType::Unknown
}
/// Guess the Minecraft version from various files in the server directory
fn detect_minecraft_version(server_path: &Path, server_type: &ServerType) -> Option<String> {
// Try from version.json if it exists
if let Ok(content) = fs::read_to_string(server_path.join("version.json")) {
if let Ok(json) = serde_json::from_str::<serde_json::Value>(&content) {
/// Helper to find the most likely server JAR file
fn find_server_jar(server_path: &Path) -> Option<PathBuf> {
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<PathBuf> = 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<String> {
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::<serde_json::Value>(&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
}
// --- Try parsing paper-global.yml for Paper servers ---
/// Guess the Minecraft version from various files in the server directory
fn detect_minecraft_version(server_path: &Path, server_type: &ServerType) -> Option<String> {
// 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::<serde_json::Value>(&content) {
if let Some(version) = json["name"].as_str() {
println!("Version found via external version.json");
return Some(version.to_string());
}
}
}
// 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
// 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);
}
}
// 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("");
// 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
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());
}
}
}
// 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");
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());
}
}
@ -472,29 +530,31 @@ fn detect_minecraft_version(server_path: &Path, server_type: &ServerType) -> Opt
}
}
// If server type is proxy, look in config files
// 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
// ... (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")) {
// 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) {
println!("Version found via velocity.toml");
return Some(version.trim().trim_matches('"').to_string());
}
}
}
}
} else {
// Try to parse config.yml for BungeeCord/Waterfall
// ... 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());
}
}
@ -503,7 +563,8 @@ fn detect_minecraft_version(server_path: &Path, server_type: &ServerType) -> Opt
}
}
// 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");
}