diff options
| author | 2026-01-18 13:34:52 +0800 | |
|---|---|---|
| committer | 2026-01-18 13:34:52 +0800 | |
| commit | 02520ca62ac5e508e8748b2445171be64f459b6c (patch) | |
| tree | a15a95be5f2f93385df36d6336f53b3f08d07a44 /src-tauri | |
| parent | 53df697ccf90cd13efc985c195dade48920cc0fa (diff) | |
| download | DropOut-02520ca62ac5e508e8748b2445171be64f459b6c.tar.gz DropOut-02520ca62ac5e508e8748b2445171be64f459b6c.zip | |
fix(ci): improve pre-commit fmt hook configuration
- Add pass_filenames: false to fmt hook
- Add -- separator for cargo fmt args
- Manually format code with cargo fmt
Diffstat (limited to 'src-tauri')
| -rw-r--r-- | src-tauri/src/core/config.rs | 36 | ||||
| -rw-r--r-- | src-tauri/src/core/java.rs | 15 | ||||
| -rw-r--r-- | src-tauri/src/core/rules.rs | 71 | ||||
| -rw-r--r-- | src-tauri/src/main.rs | 230 |
4 files changed, 262 insertions, 90 deletions
diff --git a/src-tauri/src/core/config.rs b/src-tauri/src/core/config.rs index 4c4acad..e4b9381 100644 --- a/src-tauri/src/core/config.rs +++ b/src-tauri/src/core/config.rs @@ -42,6 +42,34 @@ impl Default for AssistantConfig { } } +/// Feature-gated arguments configuration +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(default)] +pub struct FeatureFlags { + /// Demo user: enables demo-related arguments when rules require it + pub demo_user: bool, + /// Quick Play: enable quick play arguments + pub quick_play_enabled: bool, + /// Quick Play singleplayer world path (if provided) + pub quick_play_path: Option<String>, + /// Quick Play singleplayer flag + pub quick_play_singleplayer: bool, + /// Quick Play multiplayer server address (optional) + pub quick_play_multiplayer_server: Option<String>, +} + +impl Default for FeatureFlags { + fn default() -> Self { + Self { + demo_user: false, + quick_play_enabled: false, + quick_play_path: None, + quick_play_singleplayer: true, + quick_play_multiplayer_server: None, + } + } +} + #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(default)] pub struct LauncherConfig { @@ -59,6 +87,11 @@ pub struct LauncherConfig { pub log_upload_service: String, // "paste.rs" or "pastebin.com" pub pastebin_api_key: Option<String>, pub assistant: AssistantConfig, + // Storage management + pub use_shared_caches: bool, // Use global shared versions/libraries/assets + pub keep_legacy_per_instance_storage: bool, // Keep old per-instance caches (no migration) + // Feature-gated argument flags + pub feature_flags: FeatureFlags, } impl Default for LauncherConfig { @@ -78,6 +111,9 @@ impl Default for LauncherConfig { log_upload_service: "paste.rs".to_string(), pastebin_api_key: None, assistant: AssistantConfig::default(), + use_shared_caches: false, + keep_legacy_per_instance_storage: true, + feature_flags: FeatureFlags::default(), } } } diff --git a/src-tauri/src/core/java.rs b/src-tauri/src/core/java.rs index d3e1bb9..2e3c8a7 100644 --- a/src-tauri/src/core/java.rs +++ b/src-tauri/src/core/java.rs @@ -855,22 +855,19 @@ fn parse_java_version(version: &str) -> u32 { // - New format: 17.0.1, 11.0.5+10 (Java 11+) // - Format with build: 21.0.3+13-Ubuntu-0ubuntu0.24.04.1 // - Format with underscores: 1.8.0_411 - + // First, strip build metadata (everything after '+') let version_only = version.split('+').next().unwrap_or(version); - + // Remove trailing junk (like "-Ubuntu-0ubuntu0.24.04.1") - let version_only = version_only - .split('-') - .next() - .unwrap_or(version_only); - + let version_only = version_only.split('-').next().unwrap_or(version_only); + // Replace underscores with dots (1.8.0_411 -> 1.8.0.411) let normalized = version_only.replace('_', "."); - + // Split by dots let parts: Vec<&str> = normalized.split('.').collect(); - + if let Some(first) = parts.first() { if *first == "1" { // Old format: 1.8.0 -> major is 8 diff --git a/src-tauri/src/core/rules.rs b/src-tauri/src/core/rules.rs index 10a40b6..781515a 100644 --- a/src-tauri/src/core/rules.rs +++ b/src-tauri/src/core/rules.rs @@ -1,7 +1,8 @@ +use crate::core::config::FeatureFlags; use crate::core::game_version::Rule; use std::env; -pub fn is_library_allowed(rules: &Option<Vec<Rule>>) -> bool { +pub fn is_library_allowed(rules: &Option<Vec<Rule>>, features: Option<&FeatureFlags>) -> bool { // If no rules, it's allowed by default let Some(rules) = rules else { return true; @@ -39,19 +40,54 @@ pub fn is_library_allowed(rules: &Option<Vec<Rule>>) -> bool { let mut allowed = false; for rule in rules { - if rule_matches(rule) { + if rule_matches(rule, features) { allowed = rule.action == "allow"; } } allowed } -fn rule_matches(rule: &Rule) -> bool { - // Feature-based rules (e.g., is_demo_user, has_quick_plays_support, is_quick_play_singleplayer) - // are not implemented in this launcher, so we return false for any rule that has features. - // This prevents adding arguments like --demo, --quickPlayPath, --quickPlaySingleplayer, etc. - if rule.features.is_some() { - return false; +fn rule_matches(rule: &Rule, features: Option<&FeatureFlags>) -> bool { + // Feature-based rules: apply only if all listed features evaluate to true + if let Some(f) = &rule.features { + if let Some(map) = f.as_object() { + // If no feature flags provided, we cannot satisfy feature rules + let ctx = match features { + Some(ff) => ff, + None => return false, + }; + + for (key, val) in map.iter() { + let required = val.as_bool().unwrap_or(false); + // Map known features + let actual = match key.as_str() { + "is_demo_user" => ctx.demo_user, + "has_quick_plays_support" => ctx.quick_play_enabled, + "is_quick_play_singleplayer" => { + ctx.quick_play_enabled && ctx.quick_play_singleplayer + } + "is_quick_play_multiplayer" => { + ctx.quick_play_enabled + && ctx + .quick_play_multiplayer_server + .as_ref() + .map(|s| !s.is_empty()) + .unwrap_or(false) + } + _ => false, + }; + if required && !actual { + return false; + } + if !required && actual { + // If rule specifies feature must be false, but it's true, do not match + return false; + } + } + } else { + // Malformed features object + return false; + } } match &rule.os { @@ -65,28 +101,35 @@ fn rule_matches(rule: &Rule) -> bool { "windows" => env::consts::OS == "windows", _ => false, // Unknown OS name in rule }; - + if !os_match { return false; } } - + // Check architecture if specified if let Some(arch) = &os_rule.arch { let current_arch = env::consts::ARCH; - if arch != current_arch && arch != "x86_64" { - // "x86" is sometimes used for x86_64, but we only match exact arch + // Strict match: only exact architecture or known compatibility mapping + let compatible = match (arch.as_str(), current_arch) { + ("x86_64", "x86_64") => true, + ("x86", "x86") => true, + ("aarch64", "aarch64") => true, + // Treat "x86" not as matching x86_64 (be strict) + _ => arch == current_arch, + }; + if !compatible { return false; } } - + // Check version if specified (for OS version compatibility) if let Some(_version) = &os_rule.version { // Version checking would require parsing OS version strings // For now, we accept all versions (conservative approach) // In the future, parse version and compare } - + true } } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 4f9071f..6a230c9 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -296,7 +296,12 @@ async fn start_game( .as_ref() .ok_or("Version has no downloads information")?; let client_jar = &downloads.client; - let mut client_path = game_dir.join("versions"); + // Use shared caches for versions if enabled + let mut client_path = if config.use_shared_caches { + app_handle.path().app_data_dir().unwrap().join("versions") + } else { + game_dir.join("versions") + }; client_path.push(&minecraft_version); client_path.push(format!("{}.jar", minecraft_version)); @@ -309,11 +314,16 @@ async fn start_game( // --- Libraries --- println!("Processing libraries..."); - let libraries_dir = game_dir.join("libraries"); + // Use shared caches for libraries if enabled + let libraries_dir = if config.use_shared_caches { + app_handle.path().app_data_dir().unwrap().join("libraries") + } else { + 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) { + if core::rules::is_library_allowed(&lib.rules, Some(&config.feature_flags)) { // 1. Standard Library - check for explicit downloads first if let Some(downloads) = &lib.downloads { if let Some(artifact) = &downloads.artifact { @@ -336,39 +346,53 @@ async fn start_game( // 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" + // Determine candidate keys based on OS and architecture + let arch = std::env::consts::ARCH; + let mut candidates: Vec<String> = Vec::new(); + if cfg!(target_os = "linux") { + candidates.push("natives-linux".to_string()); + candidates.push(format!("natives-linux-{}", arch)); + if arch == "aarch64" { + candidates.push("natives-linux-arm64".to_string()); + } } else if cfg!(target_os = "windows") { - "natives-windows" + candidates.push("natives-windows".to_string()); + candidates.push(format!("natives-windows-{}", arch)); } 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: native_artifact.sha1, - sha256: None, - }); - - native_libs_paths.push(native_path); + candidates.push("natives-osx".to_string()); + candidates.push("natives-macos".to_string()); + candidates.push(format!("natives-macos-{}", arch)); + } + + // Pick the first available classifier key + let mut chosen: Option<core::game_version::DownloadArtifact> = None; + for key in candidates { + if let Some(native_artifact_value) = classifiers.get(&key) { + if let Ok(artifact) = + serde_json::from_value::<core::game_version::DownloadArtifact>( + native_artifact_value.clone(), + ) + { + chosen = Some(artifact); + break; + } } } + + if let Some(native_artifact) = chosen { + 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: native_artifact.sha1, + sha256: None, + }); + + native_libs_paths.push(native_path); + } } } else { // 3. Library without explicit downloads (mod loader libraries) @@ -392,7 +416,12 @@ async fn start_game( // --- Assets --- println!("Fetching asset index..."); - let assets_dir = game_dir.join("assets"); + // Use shared caches for assets if enabled + let assets_dir = if config.use_shared_caches { + app_handle.path().app_data_dir().unwrap().join("assets") + } else { + game_dir.join("assets") + }; let objects_dir = assets_dir.join("objects"); let indexes_dir = assets_dir.join("indexes"); @@ -523,7 +552,7 @@ async fn start_game( // Add libraries for lib in &version_details.libraries { - if core::rules::is_library_allowed(&lib.rules) { + if core::rules::is_library_allowed(&lib.rules, Some(&config.feature_flags)) { if let Some(downloads) = &lib.downloads { // Standard library with explicit downloads if let Some(artifact) = &downloads.artifact { @@ -556,7 +585,13 @@ async fn start_game( // First add arguments from version.json if available if let Some(args_obj) = &version_details.arguments { if let Some(jvm_args) = &args_obj.jvm { - parse_jvm_arguments(jvm_args, &mut args, &natives_path, &classpath); + parse_jvm_arguments( + jvm_args, + &mut args, + &natives_path, + &classpath, + &config.feature_flags, + ); } } @@ -588,8 +623,18 @@ async fn start_game( replacements.insert("${assets_index_name}", asset_index.id.clone()); replacements.insert("${auth_uuid}", account.uuid()); replacements.insert("${auth_access_token}", account.access_token()); - replacements.insert("${user_type}", "mojang".to_string()); - replacements.insert("${version_type}", "release".to_string()); + // Set user_type dynamically: "msa" for Microsoft accounts, "legacy" for offline + let user_type = match &account { + core::auth::Account::Microsoft(_) => "msa", + core::auth::Account::Offline(_) => "legacy", + }; + replacements.insert("${user_type}", user_type.to_string()); + // Use version_type from version JSON if available, fallback to "release" + let version_type_str = version_details + .version_type + .clone() + .unwrap_or_else(|| "release".to_string()); + replacements.insert("${version_type}", version_type_str); replacements.insert("${user_properties}", "{}".to_string()); // Correctly pass empty JSON object for user properties if let Some(minecraft_arguments) = &version_details.minecraft_arguments { @@ -622,7 +667,10 @@ async fn start_game( if let Ok(rules) = serde_json::from_value::<Vec<core::game_version::Rule>>( rules_val.clone(), ) { - core::rules::is_library_allowed(&Some(rules)) + core::rules::is_library_allowed( + &Some(rules), + Some(&config.feature_flags), + ) } else { true // Parse error, assume allow? or disallow. } @@ -815,6 +863,7 @@ fn parse_jvm_arguments( args: &mut Vec<String>, natives_path: &str, classpath: &str, + feature_flags: &core::config::FeatureFlags, ) { let mut replacements = std::collections::HashMap::new(); replacements.insert("${natives_directory}", natives_path.to_string()); @@ -840,7 +889,7 @@ fn parse_jvm_arguments( if let Ok(rules) = serde_json::from_value::<Vec<core::game_version::Rule>>(rules_val.clone()) { - core::rules::is_library_allowed(&Some(rules)) + core::rules::is_library_allowed(&Some(rules), Some(feature_flags)) } else { false } @@ -1049,7 +1098,17 @@ async fn install_version( .as_ref() .ok_or("Version has no downloads information")?; let client_jar = &downloads.client; - let mut client_path = game_dir.join("versions"); + // Use shared caches for versions if enabled + let mut client_path = if config.use_shared_caches { + window + .app_handle() + .path() + .app_data_dir() + .unwrap() + .join("versions") + } else { + game_dir.join("versions") + }; client_path.push(&minecraft_version); client_path.push(format!("{}.jar", minecraft_version)); @@ -1061,10 +1120,20 @@ async fn install_version( }); // --- Libraries --- - let libraries_dir = game_dir.join("libraries"); + // Use shared caches for libraries if enabled + let libraries_dir = if config.use_shared_caches { + window + .app_handle() + .path() + .app_data_dir() + .unwrap() + .join("libraries") + } else { + game_dir.join("libraries") + }; for lib in &version_details.libraries { - if core::rules::is_library_allowed(&lib.rules) { + if core::rules::is_library_allowed(&lib.rules, Some(&config.feature_flags)) { if let Some(downloads) = &lib.downloads { if let Some(artifact) = &downloads.artifact { let path_str = artifact @@ -1085,34 +1154,51 @@ async fn install_version( // Native Library (classifiers) if let Some(classifiers) = &downloads.classifiers { - let os_key = if cfg!(target_os = "linux") { - "natives-linux" + // Determine candidate keys based on OS and architecture + let arch = std::env::consts::ARCH; + let mut candidates: Vec<String> = Vec::new(); + if cfg!(target_os = "linux") { + candidates.push("natives-linux".to_string()); + candidates.push(format!("natives-linux-{}", arch)); + if arch == "aarch64" { + candidates.push("natives-linux-arm64".to_string()); + } } else if cfg!(target_os = "windows") { - "natives-windows" + candidates.push("natives-windows".to_string()); + candidates.push(format!("natives-windows-{}", arch)); } else if cfg!(target_os = "macos") { - "natives-osx" - } else { - "" - }; - - if let Some(native_artifact_value) = classifiers.get(os_key) { - 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(); - 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: native_artifact.sha1, - sha256: None, - }); + candidates.push("natives-osx".to_string()); + candidates.push("natives-macos".to_string()); + candidates.push(format!("natives-macos-{}", arch)); + } + + // Pick the first available classifier key + let mut chosen: Option<core::game_version::DownloadArtifact> = None; + for key in candidates { + if let Some(native_artifact_value) = classifiers.get(&key) { + if let Ok(artifact) = + serde_json::from_value::<core::game_version::DownloadArtifact>( + native_artifact_value.clone(), + ) + { + chosen = Some(artifact); + break; + } } } + + if let Some(native_artifact) = chosen { + let path_str = native_artifact.path.clone().unwrap(); + 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: native_artifact.sha1, + sha256: None, + }); + } } } else { // Library without explicit downloads (mod loader libraries) @@ -1134,7 +1220,17 @@ async fn install_version( } // --- Assets --- - let assets_dir = game_dir.join("assets"); + // Use shared caches for assets if enabled + let assets_dir = if config.use_shared_caches { + window + .app_handle() + .path() + .app_data_dir() + .unwrap() + .join("assets") + } else { + game_dir.join("assets") + }; let objects_dir = assets_dir.join("objects"); let indexes_dir = assets_dir.join("indexes"); |