From 02520ca62ac5e508e8748b2445171be64f459b6c Mon Sep 17 00:00:00 2001 From: HsiangNianian Date: Sun, 18 Jan 2026 13:34:52 +0800 Subject: 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 --- src-tauri/src/core/config.rs | 36 ++++++++++++++++++++++ src-tauri/src/core/java.rs | 15 ++++------ src-tauri/src/core/rules.rs | 71 +++++++++++++++++++++++++++++++++++--------- 3 files changed, 99 insertions(+), 23 deletions(-) (limited to 'src-tauri/src/core') 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, + /// Quick Play singleplayer flag + pub quick_play_singleplayer: bool, + /// Quick Play multiplayer server address (optional) + pub quick_play_multiplayer_server: Option, +} + +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, 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>) -> bool { +pub fn is_library_allowed(rules: &Option>, 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>) -> 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 } } -- cgit v1.2.3-70-g09d2 From 17e8dd78ca5b7aae9baa4f86d38fa755c8af21c5 Mon Sep 17 00:00:00 2001 From: HsiangNianian Date: Sun, 18 Jan 2026 13:43:12 +0800 Subject: feat(migration): implement shared cache migration with SHA1 dedup - Add migrate_to_shared_caches() with hard link preference - SHA1-based deduplication across all instances - Copy fallback for cross-filesystem scenarios - Auto-enable use_shared_caches after successful migration - UI shows statistics: moved files, hardlinks/copies, MB saved --- src-tauri/src/core/instance.rs | 224 ++++++++++++++++++++++++++++++++++ src-tauri/src/main.rs | 52 +++++++- ui/src/components/SettingsView.svelte | 31 ++++- 3 files changed, 303 insertions(+), 4 deletions(-) (limited to 'src-tauri/src/core') diff --git a/src-tauri/src/core/instance.rs b/src-tauri/src/core/instance.rs index 738dbd8..183e1cc 100644 --- a/src-tauri/src/core/instance.rs +++ b/src-tauri/src/core/instance.rs @@ -6,6 +6,7 @@ //! - Support for instance switching and isolation use serde::{Deserialize, Serialize}; +use std::collections::HashMap; use std::fs; use std::path::{Path, PathBuf}; use std::sync::Mutex; @@ -344,3 +345,226 @@ pub fn migrate_legacy_data( Ok(()) } + +/// Migrate instance caches to shared global caches +/// +/// This function deduplicates versions, libraries, and assets from all instances +/// into a global shared cache. It prefers hard links (instant, zero-copy) and +/// falls back to copying if hard links are not supported. +/// +/// # Arguments +/// * `app_handle` - Tauri app handle +/// * `instance_state` - Instance state management +/// +/// # Returns +/// * `Ok((moved_count, hardlink_count, copy_count, saved_bytes))` on success +/// * `Err(String)` on failure +pub fn migrate_to_shared_caches( + app_handle: &AppHandle, + instance_state: &InstanceState, +) -> Result<(usize, usize, usize, u64), String> { + let app_dir = app_handle.path().app_data_dir().unwrap(); + + // Global shared cache directories + let global_versions = app_dir.join("versions"); + let global_libraries = app_dir.join("libraries"); + let global_assets = app_dir.join("assets"); + + // Create global cache directories + std::fs::create_dir_all(&global_versions).map_err(|e| e.to_string())?; + std::fs::create_dir_all(&global_libraries).map_err(|e| e.to_string())?; + std::fs::create_dir_all(&global_assets).map_err(|e| e.to_string())?; + + let mut total_moved = 0; + let mut hardlink_count = 0; + let mut copy_count = 0; + let mut saved_bytes = 0u64; + + // Get all instances + let instances = instance_state.list_instances(); + + for instance in instances { + let instance_versions = instance.game_dir.join("versions"); + let instance_libraries = instance.game_dir.join("libraries"); + let instance_assets = instance.game_dir.join("assets"); + + // Migrate versions + if instance_versions.exists() { + let (moved, hardlinks, copies, bytes) = + deduplicate_directory(&instance_versions, &global_versions)?; + total_moved += moved; + hardlink_count += hardlinks; + copy_count += copies; + saved_bytes += bytes; + } + + // Migrate libraries + if instance_libraries.exists() { + let (moved, hardlinks, copies, bytes) = + deduplicate_directory(&instance_libraries, &global_libraries)?; + total_moved += moved; + hardlink_count += hardlinks; + copy_count += copies; + saved_bytes += bytes; + } + + // Migrate assets + if instance_assets.exists() { + let (moved, hardlinks, copies, bytes) = + deduplicate_directory(&instance_assets, &global_assets)?; + total_moved += moved; + hardlink_count += hardlinks; + copy_count += copies; + saved_bytes += bytes; + } + } + + Ok((total_moved, hardlink_count, copy_count, saved_bytes)) +} + +/// Deduplicate a directory tree into a global cache +/// +/// Recursively processes all files, checking SHA1 hashes for deduplication. +/// Returns (total_moved, hardlink_count, copy_count, saved_bytes) +fn deduplicate_directory( + source_dir: &Path, + dest_dir: &Path, +) -> Result<(usize, usize, usize, u64), String> { + let mut moved = 0; + let mut hardlinks = 0; + let mut copies = 0; + let mut saved_bytes = 0u64; + + // Build a hash map of existing files in dest (hash -> path) + let mut dest_hashes: HashMap = HashMap::new(); + if dest_dir.exists() { + index_directory_hashes(dest_dir, dest_dir, &mut dest_hashes)?; + } + + // Process source directory + process_directory_for_migration( + source_dir, + source_dir, + dest_dir, + &dest_hashes, + &mut moved, + &mut hardlinks, + &mut copies, + &mut saved_bytes, + )?; + + Ok((moved, hardlinks, copies, saved_bytes)) +} + +/// Index all files in a directory by their SHA1 hash +fn index_directory_hashes( + dir: &Path, + base: &Path, + hashes: &mut HashMap, +) -> Result<(), String> { + if !dir.is_dir() { + return Ok(()); + } + + for entry in std::fs::read_dir(dir).map_err(|e| e.to_string())? { + let entry = entry.map_err(|e| e.to_string())?; + let path = entry.path(); + + if path.is_dir() { + index_directory_hashes(&path, base, hashes)?; + } else if path.is_file() { + let hash = compute_file_sha1(&path)?; + hashes.insert(hash, path); + } + } + + Ok(()) +} + +/// Process directory for migration (recursive) +fn process_directory_for_migration( + current: &Path, + source_base: &Path, + dest_base: &Path, + dest_hashes: &HashMap, + moved: &mut usize, + hardlinks: &mut usize, + copies: &mut usize, + saved_bytes: &mut u64, +) -> Result<(), String> { + if !current.is_dir() { + return Ok(()); + } + + for entry in std::fs::read_dir(current).map_err(|e| e.to_string())? { + let entry = entry.map_err(|e| e.to_string())?; + let source_path = entry.path(); + + // Compute relative path + let rel_path = source_path + .strip_prefix(source_base) + .map_err(|e| e.to_string())?; + let dest_path = dest_base.join(rel_path); + + if source_path.is_dir() { + // Recurse into subdirectory + process_directory_for_migration( + &source_path, + source_base, + dest_base, + dest_hashes, + moved, + hardlinks, + copies, + saved_bytes, + )?; + } else if source_path.is_file() { + let file_size = std::fs::metadata(&source_path) + .map(|m| m.len()) + .unwrap_or(0); + + // Compute file hash + let source_hash = compute_file_sha1(&source_path)?; + + // Check if file already exists in dest with same hash + if let Some(_existing) = dest_hashes.get(&source_hash) { + // File exists, delete source (already deduplicated) + std::fs::remove_file(&source_path).map_err(|e| e.to_string())?; + *saved_bytes += file_size; + *moved += 1; + } else { + // File doesn't exist, move it + // Create parent directory in dest + if let Some(parent) = dest_path.parent() { + std::fs::create_dir_all(parent).map_err(|e| e.to_string())?; + } + + // Try hard link first + if std::fs::hard_link(&source_path, &dest_path).is_ok() { + // Hard link succeeded, remove source + std::fs::remove_file(&source_path).map_err(|e| e.to_string())?; + *hardlinks += 1; + *moved += 1; + } else { + // Hard link failed (different filesystem?), copy instead + std::fs::copy(&source_path, &dest_path).map_err(|e| e.to_string())?; + std::fs::remove_file(&source_path).map_err(|e| e.to_string())?; + *copies += 1; + *moved += 1; + } + } + } + } + + Ok(()) +} + +/// Compute SHA1 hash of a file +fn compute_file_sha1(path: &Path) -> Result { + use sha1::{Digest, Sha1}; + + let data = std::fs::read(path).map_err(|e| e.to_string())?; + let mut hasher = Sha1::new(); + hasher.update(&data); + Ok(hex::encode(hasher.finalize())) +} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 6a230c9..a506713 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -2373,6 +2373,55 @@ async fn assistant_chat_stream( .await } +/// Migrate instance caches to shared global caches +#[derive(Serialize)] +struct MigrationResult { + moved_files: usize, + hardlinks: usize, + copies: usize, + saved_bytes: u64, + saved_mb: f64, +} + +#[tauri::command] +async fn migrate_shared_caches( + window: Window, + instance_state: State<'_, core::instance::InstanceState>, + config_state: State<'_, core::config::ConfigState>, +) -> Result { + emit_log!(window, "Starting migration to shared caches...".to_string()); + + let app_handle = window.app_handle(); + let (moved, hardlinks, copies, saved_bytes) = + core::instance::migrate_to_shared_caches(app_handle, &instance_state)?; + + let saved_mb = saved_bytes as f64 / (1024.0 * 1024.0); + + emit_log!( + window, + format!( + "Migration complete: {} files moved ({} hardlinks, {} copies), {:.2} MB saved", + moved, hardlinks, copies, saved_mb + ) + ); + + // Automatically enable shared caches config + let mut config = config_state.config.lock().unwrap().clone(); + config.use_shared_caches = true; + drop(config); + *config_state.config.lock().unwrap() = config_state.config.lock().unwrap().clone(); + config_state.config.lock().unwrap().use_shared_caches = true; + config_state.save()?; + + Ok(MigrationResult { + moved_files: moved, + hardlinks, + copies, + saved_bytes, + saved_mb, + }) +} + fn main() { tauri::Builder::default() .plugin(tauri_plugin_fs::init()) @@ -2479,7 +2528,8 @@ fn main() { get_instance, set_active_instance, get_active_instance, - duplicate_instance + duplicate_instance, + migrate_shared_caches ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/ui/src/components/SettingsView.svelte b/ui/src/components/SettingsView.svelte index 0e89e25..0020506 100644 --- a/ui/src/components/SettingsView.svelte +++ b/ui/src/components/SettingsView.svelte @@ -124,12 +124,31 @@ settingsState.saveSettings(); } + let migrating = $state(false); async function runMigrationToSharedCaches() { + if (migrating) return; + migrating = true; try { - await (await import("@tauri-apps/api/core")).invoke("migrate_shared_caches"); - settingsState.loadSettings(); + const { invoke } = await import("@tauri-apps/api/core"); + const result = await invoke<{ + moved_files: number; + hardlinks: number; + copies: number; + saved_mb: number; + }>("migrate_shared_caches"); + + // Reload settings to reflect changes + await settingsState.loadSettings(); + + // Show success message + const msg = `Migration complete! ${result.moved_files} files (${result.hardlinks} hardlinks, ${result.copies} copies), ${result.saved_mb.toFixed(2)} MB saved.`; + console.log(msg); + alert(msg); } catch (e) { console.error("Migration failed:", e); + alert(`Migration failed: ${e}`); + } finally { + migrating = false; } } @@ -444,7 +463,13 @@

Run Migration

Hard-link or copy existing per-instance caches into the shared cache.

- + -- cgit v1.2.3-70-g09d2 From 6fdb730c323bcb1b052a2f9b13034603cbaf1e4d Mon Sep 17 00:00:00 2001 From: HsiangNianian Date: Sun, 18 Jan 2026 14:27:45 +0800 Subject: feat(backend): enhance instance management for editor support - Sync instance.version_id after start_game, install_fabric, install_forge - Add jvm_args_override and memory_override to Instance struct - Add file management commands: list_instance_directory, delete_instance_file, open_file_explorer - Support per-instance settings overrides (Java args, memory) --- src-tauri/src/core/instance.rs | 14 +++++ src-tauri/src/main.rs | 127 +++++++++++++++++++++++++++++++++++++++-- ui/src/types/index.ts | 7 +++ 3 files changed, 142 insertions(+), 6 deletions(-) (limited to 'src-tauri/src/core') diff --git a/src-tauri/src/core/instance.rs b/src-tauri/src/core/instance.rs index 183e1cc..573273e 100644 --- a/src-tauri/src/core/instance.rs +++ b/src-tauri/src/core/instance.rs @@ -25,6 +25,16 @@ pub struct Instance { pub notes: Option, // 备注(可选) pub mod_loader: Option, // 模组加载器类型:"fabric", "forge", "vanilla" pub mod_loader_version: Option, // 模组加载器版本 + pub jvm_args_override: Option, // JVM参数覆盖(可选) + #[serde(default)] + pub memory_override: Option, // 内存设置覆盖(可选) +} + +/// Memory settings override for an instance +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct MemoryOverride { + pub min: u32, // MB + pub max: u32, // MB } /// Configuration for all instances @@ -99,6 +109,8 @@ impl InstanceState { notes: None, mod_loader: Some("vanilla".to_string()), mod_loader_version: None, + jvm_args_override: None, + memory_override: None, }; let mut config = self.instances.lock().unwrap(); @@ -253,6 +265,8 @@ impl InstanceState { .unwrap() .as_secs() as i64, last_played: None, + jvm_args_override: source_instance.jvm_args_override.clone(), + memory_override: source_instance.memory_override.clone(), }; self.update_instance(new_instance.clone())?; diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index a506713..35e2ef5 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -1,7 +1,7 @@ // Prevents additional console window on Windows in release, DO NOT REMOVE!! #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] -use serde::Serialize; +use serde::{Deserialize, Serialize}; use std::process::Stdio; use std::sync::Mutex; use tauri::{Emitter, Manager, State, Window}; // Added Emitter @@ -854,6 +854,12 @@ async fn start_game( } }); + // Update instance's version_id to track last launched version + if let Some(mut instance) = instance_state.get_instance(&instance_id) { + instance.version_id = Some(version_id.clone()); + let _ = instance_state.update_instance(instance); + } + Ok(format!("Launched Minecraft {} successfully!", version_id)) } @@ -1693,10 +1699,11 @@ async fn install_fabric( format!("Fabric installed successfully: {}", result.id) ); - // Update Instance's mod_loader metadata + // Update Instance's mod_loader metadata and version_id if let Some(mut instance) = instance_state.get_instance(&instance_id) { instance.mod_loader = Some("fabric".to_string()); - instance.mod_loader_version = Some(loader_version); + instance.mod_loader_version = Some(loader_version.clone()); + instance.version_id = Some(result.id.clone()); instance_state.update_instance(instance)?; } @@ -2107,10 +2114,11 @@ async fn install_forge( format!("Forge installed successfully: {}", result.id) ); - // Update Instance's mod_loader metadata + // Update Instance's mod_loader metadata and version_id if let Some(mut instance) = instance_state.get_instance(&instance_id) { instance.mod_loader = Some("forge".to_string()); - instance.mod_loader_version = Some(forge_version); + instance.mod_loader_version = Some(forge_version.clone()); + instance.version_id = Some(result.id.clone()); instance_state.update_instance(instance)?; } @@ -2422,6 +2430,110 @@ async fn migrate_shared_caches( }) } +/// File information for instance file browser +#[derive(Debug, Clone, Serialize, Deserialize)] +struct FileInfo { + name: String, + path: String, + is_directory: bool, + size: u64, + modified: i64, +} + +/// List files in an instance subdirectory (mods, resourcepacks, shaderpacks, saves, screenshots) +#[tauri::command] +async fn list_instance_directory( + instance_state: State<'_, core::instance::InstanceState>, + instance_id: String, + folder: String, // "mods" | "resourcepacks" | "shaderpacks" | "saves" | "screenshots" +) -> Result, String> { + let game_dir = instance_state + .get_instance_game_dir(&instance_id) + .ok_or_else(|| format!("Instance {} not found", instance_id))?; + + let target_dir = game_dir.join(&folder); + if !target_dir.exists() { + tokio::fs::create_dir_all(&target_dir) + .await + .map_err(|e| e.to_string())?; + } + + let mut files = Vec::new(); + let mut entries = tokio::fs::read_dir(&target_dir) + .await + .map_err(|e| e.to_string())?; + + while let Some(entry) = entries.next_entry().await.map_err(|e| e.to_string())? { + let metadata = entry.metadata().await.map_err(|e| e.to_string())?; + let modified = metadata + .modified() + .ok() + .and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok()) + .map(|d| d.as_secs() as i64) + .unwrap_or(0); + + files.push(FileInfo { + name: entry.file_name().to_string_lossy().to_string(), + path: entry.path().to_string_lossy().to_string(), + is_directory: metadata.is_dir(), + size: metadata.len(), + modified, + }); + } + + // Sort: directories first, then by name + files.sort_by(|a, b| { + b.is_directory + .cmp(&a.is_directory) + .then(a.name.to_lowercase().cmp(&b.name.to_lowercase())) + }); + + Ok(files) +} + +/// Delete a file in an instance directory +#[tauri::command] +async fn delete_instance_file(path: String) -> Result<(), String> { + let path_buf = std::path::PathBuf::from(&path); + if path_buf.is_dir() { + tokio::fs::remove_dir_all(&path_buf) + .await + .map_err(|e| e.to_string())?; + } else { + tokio::fs::remove_file(&path_buf) + .await + .map_err(|e| e.to_string())?; + } + Ok(()) +} + +/// Open instance directory in system file explorer +#[tauri::command] +async fn open_file_explorer(path: String) -> Result<(), String> { + #[cfg(target_os = "windows")] + { + std::process::Command::new("explorer") + .arg(&path) + .spawn() + .map_err(|e| e.to_string())?; + } + #[cfg(target_os = "macos")] + { + std::process::Command::new("open") + .arg(&path) + .spawn() + .map_err(|e| e.to_string())?; + } + #[cfg(target_os = "linux")] + { + std::process::Command::new("xdg-open") + .arg(&path) + .spawn() + .map_err(|e| e.to_string())?; + } + Ok(()) +} + fn main() { tauri::Builder::default() .plugin(tauri_plugin_fs::init()) @@ -2529,7 +2641,10 @@ fn main() { set_active_instance, get_active_instance, duplicate_instance, - migrate_shared_caches + migrate_shared_caches, + list_instance_directory, + delete_instance_file, + open_file_explorer ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/ui/src/types/index.ts b/ui/src/types/index.ts index 858ee43..6632d58 100644 --- a/ui/src/types/index.ts +++ b/ui/src/types/index.ts @@ -214,4 +214,11 @@ export interface Instance { notes?: string; mod_loader?: string; mod_loader_version?: string; + jvm_args_override?: string; + memory_override?: MemoryOverride; +} + +export interface MemoryOverride { + min: number; // MB + max: number; // MB } -- cgit v1.2.3-70-g09d2