mod fabric;
pub mod forge;
pub mod maven;
pub mod neoforge;
mod quilt;
mod vanilla;
use std::path::Path;
use async_trait::async_trait;
use thiserror::Error;
use crate::instance::models::ModLoader;
use crate::net::{HttpClient, NetError};
#[derive(Debug, Error)]
pub enum InstallerError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Process failed: {0}")]
ProcessFailed(String),
#[error("Profile error: {0}")]
Profile(String),
}
#[derive(Debug, Error)]
pub enum InstallError {
#[error("Download error: {0}")]
Download(#[from] NetError),
#[error("Installer error: {0}")]
Installer(#[from] InstallerError),
}
pub use vanilla::VanillaInstaller;
#[derive(Debug, Clone)]
pub struct GameVersion {
pub id: String,
pub stable: bool,
}
#[async_trait]
pub trait ModLoaderInstaller: Send + Sync {
fn loader_type(&self) -> ModLoader;
async fn get_game_versions(&self, client: &HttpClient) -> Result<Vec<GameVersion>, NetError>;
async fn get_versions(
&self,
client: &HttpClient,
game_version: &str,
) -> Result<Vec<String>, NetError>;
async fn install(
&self,
client: &HttpClient,
game_version: &str,
loader_version: &str,
instance_dir: &Path,
meta_dir: &Path,
) -> Result<(), InstallError>;
}
pub(crate) fn save_profile_bytes(
meta_dir: &Path,
filename: &str,
bytes: &[u8],
) -> std::io::Result<()> {
let profiles_dir = crate::storage::MetadataPaths::new(meta_dir).loader_profiles();
std::fs::create_dir_all(&profiles_dir)?;
std::fs::write(profiles_dir.join(filename), bytes)
}
pub(crate) fn save_installer_profile(
instance_dir: &Path,
meta_dir: &Path,
version_dir_name: &str,
profile_filename: &str,
) -> Result<(), InstallerError> {
let ver_json_path = instance_dir
.join(crate::storage::MINECRAFT_DIR_NAME)
.join("versions")
.join(version_dir_name)
.join(format!("{version_dir_name}.json"));
if !ver_json_path.exists() {
tracing::debug!(
"Installer profile JSON missing: {}",
ver_json_path.display()
);
return Err(InstallerError::Profile(format!(
"Version JSON not found at {}",
ver_json_path.display()
)));
}
tracing::debug!(
"Saving installer profile {} from {}",
profile_filename,
ver_json_path.display()
);
let raw = std::fs::read(&ver_json_path)?;
let profiles_dir = crate::storage::MetadataPaths::new(meta_dir).loader_profiles();
std::fs::create_dir_all(&profiles_dir)?;
let profile_path = profiles_dir.join(profile_filename);
std::fs::write(&profile_path, &raw)?;
tracing::debug!(
"Saved installer profile to {} ({} bytes)",
profile_path.display(),
raw.len()
);
Ok(())
}
pub fn get_installer(loader: ModLoader) -> Box<dyn ModLoaderInstaller + Send + Sync> {
match loader {
ModLoader::Vanilla => Box::new(vanilla::VanillaInstaller),
ModLoader::Fabric => Box::new(fabric::FabricInstaller),
ModLoader::Forge => Box::new(forge::ForgeInstaller),
ModLoader::NeoForge => Box::new(neoforge::NeoForgeInstaller),
ModLoader::Quilt => Box::new(quilt::QuiltInstaller),
}
}
#[cfg(test)]
#[path = "../tests/loader/installers.rs"]
mod tests;