use std::{collections::HashMap, path::PathBuf}; use serde::{Deserialize, Serialize}; use crate::{ State, install::{InstallPhaseDetails, InstallProgressReporter}, pack::{ self, import::{self, finish_import}, install_from::CreatePackDescription, }, prelude::ModLoader, state::{ AppliedContentSetPatch, EditInstance, InstanceInstallStage, InstanceLink, }, util::io, }; #[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, } #[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 client_side: Option, pub server_side: Option, pub categories: Vec, } #[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 = serde_json::from_str::( &io::read_any_encoding_to_string( &instance_folder.join("instance.json"), ) .await .unwrap_or(("".into(), encoding_rs::UTF_8)) .0, ); if let Err(e) = instance { tracing::warn!( "Could not parse instance.json at {}: {}", instance_folder.display(), e ); false } else { true } } #[tracing::instrument] pub async fn import_atlauncher_dir( atlauncher_base_path: PathBuf, atlauncher_instance_path: PathBuf, instance_id: &str, reporter: InstallProgressReporter, details: InstallPhaseDetails, symlink: bool, ) -> crate::Result<()> { let atinstance = serde_json::from_str::( &io::read_any_encoding_to_string( &atlauncher_instance_path.join("instance.json"), ) .await .unwrap_or(("".into(), encoding_rs::UTF_8)) .0, )?; 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, }; let description = CreatePackDescription { icon, override_title: Some(atinstance.launcher.name.clone()), project_id: None, version_id: None, instance_id: instance_id.to_string(), source_filename: None, }; let backup_name = format!( "ATLauncher-{}", atlauncher_instance_path .file_name() .map(|name| name.to_string_lossy()) .unwrap_or_default() ); let minecraft_folder = atlauncher_instance_path; import_atlauncher_unmanaged( instance_id, minecraft_folder, backup_name, description, atinstance, reporter, details, symlink, ) .await?; Ok(()) } async fn import_atlauncher_unmanaged( instance_id: &str, minecraft_folder: PathBuf, backup_name: String, description: CreatePackDescription, atinstance: ATInstance, reporter: InstallProgressReporter, details: InstallPhaseDetails, symlink: bool, ) -> 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::launcher::get_loader_version_from_profile( &game_version, mod_loader, Some(&atinstance.launcher.loader_version.version), ) .await? } else { None }; let link = match (&description.project_id, &description.version_id) { (Some(project_id), Some(version_id)) => { Some(InstanceLink::ModrinthModpack { project_id: project_id.clone(), version_id: version_id.clone(), }) } _ => None, }; crate::api::instance::edit( instance_id, EditInstance { install_stage: Some(InstanceInstallStage::PackInstalling), name: Some( description .override_title .clone() .unwrap_or_else(|| backup_name.to_string()), ), icon_path: Some( description .icon .clone() .map(|x| x.to_string_lossy().to_string()), ), link, content_set_patch: Some(AppliedContentSetPatch { source_kind: None, game_version: Some(game_version.clone()), protocol_version: Some(None), loader: Some(mod_loader), loader_version: Some(loader_version.clone().map(|x| x.id)), }), ..EditInstance::default() }, ) .await?; // Moves .minecraft folder over (ie: overrides such as resourcepacks, mods, etc) let state = State::get().await?; finish_import( instance_id, minecraft_folder, &state.io_semaphore, reporter, details, symlink, ) .await?; Ok(()) }