feat: Enhance Minecraft version detection with JAR file analysis and improved fallback mechanisms
This commit is contained in:
parent
fc96a10397
commit
4adb291592
@ -11,6 +11,7 @@ use sha2::{Sha256, Digest};
|
|||||||
use reqwest;
|
use reqwest;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use tauri::AppHandle;
|
use tauri::AppHandle;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
// Add the crawlers module
|
// Add the crawlers module
|
||||||
mod crawlers;
|
mod crawlers;
|
||||||
@ -395,115 +396,175 @@ fn detect_server_type(server_path: &Path) -> ServerType {
|
|||||||
ServerType::Unknown
|
ServerType::Unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
}
|
||||||
|
|
||||||
/// Guess the Minecraft version from various files in the server directory
|
/// Guess the Minecraft version from various files in the server directory
|
||||||
fn detect_minecraft_version(server_path: &Path, server_type: &ServerType) -> Option<String> {
|
fn detect_minecraft_version(server_path: &Path, server_type: &ServerType) -> Option<String> {
|
||||||
// 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(content) = fs::read_to_string(server_path.join("version.json")) {
|
||||||
if let Ok(json) = serde_json::from_str::<serde_json::Value>(&content) {
|
if let Ok(json) = serde_json::from_str::<serde_json::Value>(&content) {
|
||||||
if let Some(version) = json["name"].as_str() {
|
if let Some(version) = json["name"].as_str() {
|
||||||
|
println!("Version found via external version.json");
|
||||||
return Some(version.to_string());
|
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 {
|
if server_type == &ServerType::Paper {
|
||||||
let paper_global_path = server_path.join("config").join("paper-global.yml");
|
let paper_global_path = server_path.join("config").join("paper-global.yml");
|
||||||
if paper_global_path.exists() {
|
if paper_global_path.exists() {
|
||||||
if let Ok(content) = fs::read_to_string(&paper_global_path) {
|
if let Ok(content) = fs::read_to_string(&paper_global_path) {
|
||||||
// Use yaml_rust::YamlLoader here
|
|
||||||
match yaml_rust::YamlLoader::load_from_str(&content) {
|
match yaml_rust::YamlLoader::load_from_str(&content) {
|
||||||
Ok(docs) if !docs.is_empty() => {
|
Ok(docs) if !docs.is_empty() => {
|
||||||
let doc = &docs[0];
|
let doc = &docs[0];
|
||||||
// Common location for MC version, might differ
|
|
||||||
if let Some(version) = doc["misc"]["paper-version"].as_str() {
|
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);
|
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());
|
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() {
|
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());
|
return Some(version.to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => println!("Failed to parse paper-global.yml: {}", e),
|
||||||
println!("Failed to parse paper-global.yml: {}", e);
|
|
||||||
}
|
|
||||||
_ => { /* Empty or invalid YAML */ }
|
_ => { /* 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 Ok(entries) = fs::read_dir(server_path) {
|
if let Some(server_jar_path) = find_server_jar(server_path) {
|
||||||
for entry in entries {
|
if let Some(version) = read_version_from_jar(&server_jar_path) {
|
||||||
if let Ok(entry) = entry {
|
println!("Version found via internal version.json in {}", server_jar_path.display());
|
||||||
let path = entry.path();
|
return Some(version);
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 ||
|
if server_type == &ServerType::BungeeCord ||
|
||||||
server_type == &ServerType::Waterfall ||
|
server_type == &ServerType::Waterfall ||
|
||||||
server_type == &ServerType::Velocity {
|
server_type == &ServerType::Velocity {
|
||||||
// Velocity uses TOML, others use YAML
|
// ... (existing proxy config logic remains here) ...
|
||||||
if server_type == &ServerType::Velocity {
|
if server_type == &ServerType::Velocity {
|
||||||
if let Ok(content) = fs::read_to_string(server_path.join("velocity.toml")) {
|
// ... velocity.toml check ...
|
||||||
// Very basic TOML parsing just for this field
|
if let Ok(content) = fs::read_to_string(server_path.join("velocity.toml")) {
|
||||||
for line in content.lines() {
|
for line in content.lines() {
|
||||||
if line.contains("minecraft-version") {
|
if line.contains("minecraft-version") {
|
||||||
if let Some(version) = line.split('=').nth(1) {
|
if let Some(version) = line.split('=').nth(1) {
|
||||||
return Some(version.trim().trim_matches('"').to_string());
|
println!("Version found via velocity.toml");
|
||||||
}
|
return Some(version.trim().trim_matches('"').to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
// Try to parse config.yml for BungeeCord/Waterfall
|
} else {
|
||||||
if let Ok(content) = fs::read_to_string(server_path.join("config.yml")) {
|
// ... config.yml check ...
|
||||||
if let Ok(docs) = YamlLoader::load_from_str(&content) {
|
if let Ok(content) = fs::read_to_string(server_path.join("config.yml")) {
|
||||||
if !docs.is_empty() {
|
if let Ok(docs) = YamlLoader::load_from_str(&content) {
|
||||||
let doc = &docs[0];
|
if !docs.is_empty() {
|
||||||
if let Some(version) = doc["minecraft_version"].as_str() {
|
let doc = &docs[0];
|
||||||
return Some(version.to_string());
|
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
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -959,3 +1020,5 @@ pub fn run() {
|
|||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user