use std::{collections::HashMap, path::PathBuf}; use serde::{Deserialize, Serialize}; use crate::{ event::LoadingBarId, pack::{ self, import::{self, copy_dotminecraft}, install_from::CreatePackDescription, }, prelude::{ModLoader, ProfilePathId}, state::{LinkedData, ProfileInstallStage}, util::io, State, }; #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ATInstance { pub id: String, // minecraft version id ie: 1.12.1, not a name pub launcher: ATLauncher, pub java_version: ATJavaVersion, } #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ATLauncher { pub name: String, pub pack: String, pub version: String, // ie: 1.6 pub loader_version: ATLauncherLoaderVersion, pub modrinth_project: Option, pub modrinth_version: Option, pub modrinth_manifest: Option, pub mods: Vec, } #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ATJavaVersion { pub major_version: u8, pub component: String, } #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct ATLauncherLoaderVersion { pub r#type: String, pub version: String, } #[derive(Serialize, Deserialize, Debug)] pub struct ATLauncherModrinthProject { pub id: String, pub slug: String, pub project_type: String, pub team: String, pub title: String, pub description: String, pub body: String, pub client_side: Option, pub server_side: Option, pub categories: Vec, pub icon_url: String, } #[derive(Serialize, Deserialize, Debug)] pub struct ATLauncherModrinthVersion { pub id: String, pub project_id: String, pub name: String, pub version_number: String, } #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct ATLauncherModrinthVersionFile { pub hashes: HashMap, pub url: String, pub filename: String, pub primary: bool, pub size: u64, } #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct ATLauncherModrinthVersionDependency { pub project_id: Option, pub version_id: Option, } #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct ATLauncherMod { pub name: String, pub version: String, pub file: String, pub modrinth_project: Option, pub modrinth_version: Option, } // Check if folder has a instance.json that parses pub async fn is_valid_atlauncher(instance_folder: PathBuf) -> bool { let instance: String = io::read_to_string(&instance_folder.join("instance.json")) .await .unwrap_or("".to_string()); let instance: Result = serde_json::from_str::(&instance); instance.is_ok() } #[tracing::instrument] #[theseus_macros::debug_pin] pub async fn import_atlauncher( atlauncher_base_path: PathBuf, // path to base atlauncher folder instance_folder: String, // instance folder in atlauncher_base_path profile_path: ProfilePathId, // path to profile ) -> crate::Result<()> { let atlauncher_instance_path = atlauncher_base_path .join("instances") .join(instance_folder.clone()); // Load instance.json let atinstance: String = io::read_to_string(&atlauncher_instance_path.join("instance.json")) .await?; let atinstance: ATInstance = serde_json::from_str::(&atinstance)?; // Icon path should be {instance_folder}/instance.png if it exists, // Second possibility is ATLauncher/configs/images/{safe_pack_name}.png (safe pack name is alphanumeric lowercase) let icon_path_primary = atlauncher_instance_path.join("instance.png"); let safe_pack_name = atinstance .launcher .pack .replace(|c: char| !c.is_alphanumeric(), "") .to_lowercase(); let icon_path_secondary = atlauncher_base_path .join("configs") .join("images") .join(safe_pack_name + ".png"); let icon = match (icon_path_primary.exists(), icon_path_secondary.exists()) { (true, _) => import::recache_icon(icon_path_primary).await?, (_, true) => import::recache_icon(icon_path_secondary).await?, _ => None, }; // Create description from instance.cfg let description = CreatePackDescription { icon, override_title: Some(atinstance.launcher.name.clone()), project_id: None, version_id: None, existing_loading_bar: None, profile_path: profile_path.clone(), }; let backup_name = format!("ATLauncher-{}", instance_folder); let minecraft_folder = atlauncher_instance_path; import_atlauncher_unmanaged( profile_path, minecraft_folder, backup_name, description, atinstance, None, ) .await?; Ok(()) } async fn import_atlauncher_unmanaged( profile_path: ProfilePathId, minecraft_folder: PathBuf, backup_name: String, description: CreatePackDescription, atinstance: ATInstance, existing_loading_bar: Option, ) -> crate::Result<()> { let mod_loader = format!( "\"{}\"", atinstance.launcher.loader_version.r#type.to_lowercase() ); let mod_loader: ModLoader = serde_json::from_str::(&mod_loader) .map_err(|_| { crate::ErrorKind::InputError(format!( "Could not parse mod loader type: {}", mod_loader )) })?; let game_version = atinstance.id; let loader_version = if mod_loader != ModLoader::Vanilla { crate::profile::create::get_loader_version_from_loader( game_version.clone(), mod_loader, Some(atinstance.launcher.loader_version.version.clone()), ) .await? } else { None }; // Set profile data to created default profile crate::api::profile::edit(&profile_path, |prof| { prof.metadata.name = description .override_title .clone() .unwrap_or_else(|| backup_name.to_string()); prof.install_stage = ProfileInstallStage::PackInstalling; prof.metadata.linked_data = Some(LinkedData { project_id: description.project_id.clone(), version_id: description.version_id.clone(), }); prof.metadata.icon = description.icon.clone(); prof.metadata.game_version = game_version.clone(); prof.metadata.loader_version = loader_version.clone(); prof.metadata.loader = mod_loader; async { Ok(()) } }) .await?; // Moves .minecraft folder over (ie: overrides such as resourcepacks, mods, etc) let state = State::get().await?; copy_dotminecraft( profile_path.clone(), minecraft_folder, &state.io_semaphore, ) .await?; if let Some(profile_val) = crate::api::profile::get(&profile_path, None).await? { crate::launcher::install_minecraft(&profile_val, existing_loading_bar) .await?; State::sync().await?; } Ok(()) }