use std::path::PathBuf; use serde::{Deserialize, Serialize}; use crate::{ prelude::{ModLoader, ProfilePathId}, state::ProfileInstallStage, util::{ fetch::{fetch, write_cached_icon}, io, }, State, }; use super::{copy_dotminecraft, recache_icon}; #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct FlameManifest { pub manifest_version: u8, pub name: String, pub minecraft: FlameMinecraft, } #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct FlameMinecraft { pub version: String, pub mod_loaders: Vec, } #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct FlameModLoader { pub id: String, pub primary: bool, } #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct MinecraftInstance { pub name: Option, pub profile_image_path: Option, pub installed_modpack: Option, pub game_version: String, // Minecraft game version. Non-prioritized, use this if Vanilla } #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "camelCase")] pub struct InstalledModpack { pub thumbnail_url: Option, } // Check if folder has a minecraftinstance.json that parses pub async fn is_valid_curseforge(instance_folder: PathBuf) -> bool { let minecraftinstance: String = io::read_to_string(&instance_folder.join("minecraftinstance.json")) .await .unwrap_or("".to_string()); let minecraftinstance: Result = serde_json::from_str::(&minecraftinstance); minecraftinstance.is_ok() } pub async fn import_curseforge( curseforge_instance_folder: PathBuf, // instance's folder profile_path: ProfilePathId, // path to profile ) -> crate::Result<()> { // Load minecraftinstance.json let minecraft_instance: String = io::read_to_string( &curseforge_instance_folder.join("minecraftinstance.json"), ) .await?; let minecraft_instance: MinecraftInstance = serde_json::from_str::(&minecraft_instance)?; let override_title: Option = minecraft_instance.name.clone(); let backup_name = format!( "Curseforge-{}", curseforge_instance_folder .file_name() .map(|a| a.to_string_lossy().to_string()) .unwrap_or("Unknown".to_string()) ); let state = State::get().await?; // Recache Curseforge Icon if it exists let mut icon = None; if let Some(icon_path) = minecraft_instance.profile_image_path.clone() { icon = recache_icon(icon_path).await?; } else if let Some(InstalledModpack { thumbnail_url: Some(thumbnail_url), }) = minecraft_instance.installed_modpack.clone() { let icon_bytes = fetch(&thumbnail_url, None, &state.fetch_semaphore).await?; let filename = thumbnail_url.rsplit('/').last(); if let Some(filename) = filename { icon = Some( write_cached_icon( filename, &state.directories.caches_dir(), icon_bytes, &state.io_semaphore, ) .await?, ); } } // Curseforge vanilla profile may not have a manifest.json, so we allow it to not exist if curseforge_instance_folder.join("manifest.json").exists() { // Load manifest.json let cf_manifest: String = io::read_to_string( &curseforge_instance_folder.join("manifest.json"), ) .await?; let cf_manifest: FlameManifest = serde_json::from_str::(&cf_manifest)?; let game_version = cf_manifest.minecraft.version; // CF allows Forge, Fabric, and Vanilla let mut mod_loader = None; let mut loader_version = None; for loader in cf_manifest.minecraft.mod_loaders { match loader.id.split_once('-') { Some(("forge", version)) => { mod_loader = Some(ModLoader::Forge); loader_version = Some(version.to_string()); } Some(("fabric", version)) => { mod_loader = Some(ModLoader::Fabric); loader_version = Some(version.to_string()); } _ => {} } } let mod_loader = mod_loader.unwrap_or(ModLoader::Vanilla); let loader_version = if mod_loader != ModLoader::Vanilla { crate::profile::create::get_loader_version_from_loader( game_version.clone(), mod_loader, loader_version, ) .await? } else { None }; // Set profile data to created default profile crate::api::profile::edit(&profile_path, |prof| { prof.metadata.name = override_title .clone() .unwrap_or_else(|| backup_name.to_string()); prof.install_stage = ProfileInstallStage::PackInstalling; prof.metadata.icon = icon.clone(); prof.metadata.game_version = game_version.clone(); prof.metadata.loader_version = loader_version.clone(); prof.metadata.loader = mod_loader; async { Ok(()) } }) .await?; } else { // If no manifest is found, it's a vanilla profile crate::api::profile::edit(&profile_path, |prof| { prof.metadata.name = override_title .clone() .unwrap_or_else(|| backup_name.to_string()); prof.metadata.icon = icon.clone(); prof.metadata.game_version = minecraft_instance.game_version.clone(); prof.metadata.loader_version = None; prof.metadata.loader = ModLoader::Vanilla; async { Ok(()) } }) .await?; } // Copy in contained folders as overrides let state = State::get().await?; copy_dotminecraft( profile_path.clone(), curseforge_instance_folder, &state.io_semaphore, ) .await?; if let Some(profile_val) = crate::api::profile::get(&profile_path, None).await? { crate::launcher::install_minecraft(&profile_val, None).await?; State::sync().await?; } Ok(()) }