diff --git a/gh_assets/minecraft-tab.png b/gh_assets/minecraft-tab.png new file mode 100644 index 00000000..71c0ca7a Binary files /dev/null and b/gh_assets/minecraft-tab.png differ diff --git a/gh_assets/vanilla-integration-1.png b/gh_assets/vanilla-integration-1.png new file mode 100644 index 00000000..8198f8e7 Binary files /dev/null and b/gh_assets/vanilla-integration-1.png differ diff --git a/gh_assets/vanilla-integration-2.png b/gh_assets/vanilla-integration-2.png new file mode 100644 index 00000000..c92c684b Binary files /dev/null and b/gh_assets/vanilla-integration-2.png differ diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 00000000..2ab732e6 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly-2024-11-01" diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index c8c32f85..f6240888 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1036,6 +1036,15 @@ dependencies = [ "dirs-sys 0.4.1", ] +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys 0.4.1", +] + [[package]] name = "dirs" version = "6.0.0" @@ -2426,6 +2435,7 @@ dependencies = [ "base16ct", "chrono", "directories", + "dirs 5.0.1", "futures", "md5", "oauth2", @@ -4799,7 +4809,7 @@ checksum = "e7b0bc1aec81bda6bc455ea98fcaed26b3c98c1648c627ad6ff1c704e8bf8cbc" dependencies = [ "anyhow", "bytes", - "dirs", + "dirs 6.0.0", "dunce", "embed_plist", "futures-util", @@ -4850,7 +4860,7 @@ checksum = "d7a0350f0df1db385ca5c02888a83e0e66655c245b7443db8b78a70da7d7f8fc" dependencies = [ "anyhow", "cargo_toml", - "dirs", + "dirs 6.0.0", "glob", "heck 0.5.0", "json-patch", @@ -5016,7 +5026,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73f05c38afd77a4b8fd98e8fb6f1cdbb5fbb8a46ba181eb2758b05321e3c6209" dependencies = [ "base64 0.22.1", - "dirs", + "dirs 6.0.0", "flate2", "futures-util", "http", @@ -5535,7 +5545,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7eee98ec5c90daf179d55c20a49d8c0d043054ce7c26336c09a24d31f14fa0" dependencies = [ "crossbeam-channel", - "dirs", + "dirs 6.0.0", "libappindicator", "muda", "objc2 0.6.1", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index ca1d7916..55422aef 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -30,6 +30,7 @@ backon = { version = "1.3.0", default-features = false, features = ["tokio-sleep # Generic void = "1" directories = "5.0" +dirs = "5.0" once_cell = "1.16.0" # FS libs diff --git a/src-tauri/src/app/gui/commands/client.rs b/src-tauri/src/app/gui/commands/client.rs index b67f0973..cee8f11e 100644 --- a/src-tauri/src/app/gui/commands/client.rs +++ b/src-tauri/src/app/gui/commands/client.rs @@ -350,6 +350,7 @@ pub(crate) async fn run_client( client, client_account, skip_advertisement, + vanilla_integration: options.start_options.vanilla_integration, }; thread::spawn(move || { diff --git a/src-tauri/src/app/gui/commands/mod.rs b/src-tauri/src/app/gui/commands/mod.rs index 4c3783ae..f8b24a2c 100644 --- a/src-tauri/src/app/gui/commands/mod.rs +++ b/src-tauri/src/app/gui/commands/mod.rs @@ -21,8 +21,10 @@ pub(crate) mod auth; pub(crate) mod client; pub(crate) mod data; pub(crate) mod system; +pub(crate) mod vanilla; pub(crate) use auth::*; pub(crate) use client::*; pub(crate) use data::*; pub(crate) use system::*; +pub(crate) use vanilla::*; diff --git a/src-tauri/src/app/gui/commands/vanilla.rs b/src-tauri/src/app/gui/commands/vanilla.rs new file mode 100644 index 00000000..7f008d27 --- /dev/null +++ b/src-tauri/src/app/gui/commands/vanilla.rs @@ -0,0 +1,81 @@ +/* + * This file is part of LiquidLauncher (https://github.com/CCBlueX/LiquidLauncher) + * + * Copyright (c) 2015 - 2024 CCBlueX + * + * LiquidLauncher is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LiquidLauncher is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LiquidLauncher. If not, see . + */ + +use std::path::PathBuf; +use serde::Serialize; + +#[derive(Serialize)] +pub struct VanillaStatus { + pub found: bool, + pub path: String, + pub saves_count: usize, + pub resource_packs_count: usize, + pub shader_packs_count: usize, +} + +fn get_vanilla_minecraft_dir() -> Option { + #[cfg(target_os = "windows")] + { + dirs::data_dir().map(|p| p.join(".minecraft")) + } + #[cfg(target_os = "macos")] + { + dirs::home_dir().map(|p| p.join("Library/Application Support/minecraft")) + } + #[cfg(target_os = "linux")] + { + dirs::home_dir().map(|p| p.join(".minecraft")) + } +} + +fn count_entries(path: &PathBuf) -> usize { + path.read_dir().map(|r| r.count()).unwrap_or(0) +} + +#[tauri::command] +pub(crate) async fn get_vanilla_status(custom_path: Option) -> Result { + let mc_dir = if let Some(ref path) = custom_path { + if !path.is_empty() { + Some(PathBuf::from(path)) + } else { + get_vanilla_minecraft_dir() + } + } else { + get_vanilla_minecraft_dir() + }; + + match mc_dir { + Some(path) if path.exists() => { + Ok(VanillaStatus { + found: true, + path: path.to_string_lossy().to_string(), + saves_count: count_entries(&path.join("saves")), + resource_packs_count: count_entries(&path.join("resourcepacks")), + shader_packs_count: count_entries(&path.join("shaderpacks")), + }) + } + _ => Ok(VanillaStatus { + found: false, + path: String::new(), + saves_count: 0, + resource_packs_count: 0, + shader_packs_count: 0, + }), + } +} diff --git a/src-tauri/src/app/gui/mod.rs b/src-tauri/src/app/gui/mod.rs index 8e06dd85..51f46f0d 100644 --- a/src-tauri/src/app/gui/mod.rs +++ b/src-tauri/src/app/gui/mod.rs @@ -75,7 +75,8 @@ pub fn gui_main() { get_launcher_version, get_custom_mods, install_custom_mod, - delete_custom_mod + delete_custom_mod, + get_vanilla_status ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/src-tauri/src/app/options.rs b/src-tauri/src/app/options.rs index 98898740..714fc9ef 100644 --- a/src-tauri/src/app/options.rs +++ b/src-tauri/src/app/options.rs @@ -51,6 +51,20 @@ pub(crate) struct StartOptions { pub jvm_args: Option>, #[serde(rename = "memory", default = "default_memory")] pub memory: u64, + #[serde(rename = "vanillaIntegration", default)] + pub vanilla_integration: VanillaIntegration, +} + +#[derive(Clone, Serialize, Deserialize, Default)] +pub struct VanillaIntegration { + #[serde(rename = "customPath", default)] + pub custom_path: String, + #[serde(rename = "useVanillaSaves", default)] + pub use_vanilla_saves: bool, + #[serde(rename = "useVanillaResourcePacks", default)] + pub use_vanilla_resource_packs: bool, + #[serde(rename = "useVanillaShaderPacks", default)] + pub use_vanilla_shader_packs: bool, } #[derive(Serialize, Deserialize)] @@ -118,6 +132,7 @@ impl Options { minecraft_account: legacy.current_account, jvm_args: None, // No equivalent in legacy format memory: 4096, // No equivalent in legacy format - default to 4GB + vanilla_integration: VanillaIntegration::default(), }, version_options: VersionOptions { branch_name: None, // Force recommended branch @@ -153,6 +168,7 @@ impl Default for StartOptions { custom_data_path: String::new(), jvm_args: None, memory: 4096, + vanilla_integration: VanillaIntegration::default(), } } } diff --git a/src-tauri/src/minecraft/launcher/mod.rs b/src-tauri/src/minecraft/launcher/mod.rs index 7e7528dc..281795a8 100644 --- a/src-tauri/src/minecraft/launcher/mod.rs +++ b/src-tauri/src/minecraft/launcher/mod.rs @@ -18,7 +18,8 @@ */ use std::collections::HashSet; -use std::path::Path; +use std::fs; +use std::path::{Path, PathBuf}; use std::process::exit; @@ -103,6 +104,9 @@ pub async fn launch( let assets_folder = join_and_mkdir!(data, "assets"); let game_dir = join_and_mkdir_vec!(data, vec!["gameDir", &*manifest.build.branch]); + // Setup vanilla integration symlinks + setup_vanilla_integration(&game_dir, &launching_parameter.vanilla_integration, &launcher_data)?; + let java_bin = load_jre( &runtimes_folder, &manifest, @@ -276,8 +280,11 @@ pub struct StartParameter { pub client: Client, pub client_account: Option, pub skip_advertisement: bool, + pub vanilla_integration: VanillaIntegration, } +use crate::app::options::VanillaIntegration; + fn process_templates Result<()>>( input: &String, retriever: F, @@ -325,3 +332,70 @@ fn process_templates Result<()>>( Ok(output) } + +fn setup_vanilla_integration( + game_dir: &Path, + integration: &VanillaIntegration, + launcher_data: &LauncherData, +) -> Result<()> { + let vanilla_dir = if !integration.custom_path.is_empty() { + let custom = PathBuf::from(&integration.custom_path); + if custom.exists() { Some(custom) } else { None } + } else { + get_vanilla_minecraft_dir().filter(|p| p.exists()) + }; + + let vanilla_dir = match vanilla_dir { + Some(p) => p, + None => return Ok(()), + }; + + let links = [ + ("saves", integration.use_vanilla_saves), + ("resourcepacks", integration.use_vanilla_resource_packs), + ("shaderpacks", integration.use_vanilla_shader_packs), + ]; + + for (folder, enabled) in links { + let target = game_dir.join(folder); + let source = vanilla_dir.join(folder); + + if enabled && source.exists() { + if target.exists() { + if target.is_symlink() { + continue; + } + std::fs::remove_dir_all(&target).ok(); + } + + #[cfg(unix)] + std::os::unix::fs::symlink(&source, &target)?; + + #[cfg(windows)] + std::os::windows::fs::symlink_dir(&source, &target)?; + + launcher_data.log(&format!("Linked vanilla {} folder", folder)); + } else if !enabled && target.is_symlink() { + fs::remove_file(&target).ok(); + fs::create_dir_all(&target)?; + launcher_data.log(&format!("Unlinked vanilla {} folder", folder)); + } + } + + Ok(()) +} + +fn get_vanilla_minecraft_dir() -> Option { + #[cfg(target_os = "windows")] + { + dirs::data_dir().map(|p| p.join(".minecraft")) + } + #[cfg(target_os = "macos")] + { + dirs::home_dir().map(|p| p.join("Library/Application Support/minecraft")) + } + #[cfg(target_os = "linux")] + { + dirs::home_dir().map(|p| p.join(".minecraft")) + } +} diff --git a/src/lib/main/settings/Settings.svelte b/src/lib/main/settings/Settings.svelte index d34d1c7b..5ed4da3a 100644 --- a/src/lib/main/settings/Settings.svelte +++ b/src/lib/main/settings/Settings.svelte @@ -2,6 +2,7 @@ import {createEventDispatcher} from "svelte"; import GeneralSettings from "./GeneralSettings.svelte"; import PremiumSettings from "./PremiumSettings.svelte"; + import VanillaIntegration from "./VanillaIntegration.svelte"; import SettingsContainer from "../../settings/SettingsContainer.svelte"; import Tabs from "../../settings/tab/Tabs.svelte"; @@ -17,7 +18,7 @@ on:hideSettings={() => dispatch('hide')} > @@ -26,6 +27,10 @@ + {:else if activeSettingsTab === "Minecraft"} + {:else if activeSettingsTab === "Premium"} + import ToggleSetting from "../../settings/ToggleSetting.svelte"; + import DirectorySelectorSetting from "../../settings/DirectorySelectorSetting.svelte"; + import {onMount} from "svelte"; + import {invoke} from "@tauri-apps/api/core"; + + export let options; + + let vanillaStatus = null; + + async function refreshStatus() { + try { + vanillaStatus = await invoke("get_vanilla_status", { + customPath: options.start.vanillaIntegration.customPath || null + }); + } catch (e) { + console.error("Failed to get vanilla status:", e); + } + } + + onMount(refreshStatus); + + $: if (options.start.vanillaIntegration.customPath !== undefined) { + refreshStatus(); + } + + + + +{#if vanillaStatus?.found} +
+ {vanillaStatus.saves_count} worlds • {vanillaStatus.resource_packs_count} resource packs • {vanillaStatus.shader_packs_count} shader packs +
+ + + + + + +{:else} +
+ Minecraft installation not found +
+{/if} + +