pub mod authlib;
pub mod curseforge;
pub mod fabric;
pub mod forge;
pub mod mcmod;
pub mod modrinth;
pub mod optifine;
pub mod quiltmc;
pub mod structs;
pub mod vanilla;
use std::{fmt::Display, path::Path, str::FromStr};
use anyhow::Context;
use async_trait::async_trait;
pub use authlib::AuthlibDownloadExt;
pub use fabric::FabricDownloadExt;
pub use forge::ForgeDownloadExt;
pub use optifine::OptifineDownloadExt;
pub use quiltmc::QuiltMCDownloadExt;
use serde::{Deserialize, Serialize};
pub use vanilla::VanillaDownloadExt;
use self::structs::VersionInfo;
use crate::{path::*, prelude::*, progress::*};
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
pub enum DownloadSource {
Default,
BMCLAPI,
MCBBS,
Custom(url::Url),
}
impl Default for DownloadSource {
fn default() -> Self {
Self::Default
}
}
impl Display for DownloadSource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
DownloadSource::Default => "默认(官方)下载源",
DownloadSource::BMCLAPI => "BMCLAPI 下载源",
DownloadSource::MCBBS => "MCBBS 下载源",
DownloadSource::Custom(_) => "自定义",
}
)
}
}
impl FromStr for DownloadSource {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"Offical" => Ok(Self::Default),
"BMCLAPI" => Ok(Self::BMCLAPI),
"MCBBS" => Ok(Self::MCBBS),
s => {
let url = s.parse::<url::Url>();
if let Ok(url) = url {
Ok(Self::Custom(url))
} else {
Ok(Self::Default)
}
}
}
}
}
#[derive(Debug)]
pub struct Downloader<R> {
pub source: DownloadSource,
pub(crate) minecraft_path: String,
pub(crate) minecraft_library_path: String,
pub(crate) minecraft_version_path: String,
pub(crate) minecraft_assets_path: String,
pub game_independent: bool,
pub verify_data: bool,
pub java_path: String,
parallel_amount: usize,
pub(crate) parallel_lock: inner_future::lock::Semaphore,
pub reporter: Option<R>,
}
impl<R> Downloader<R> {
pub fn set_minecraft_path(&mut self, dot_minecraft_path: impl AsRef<Path>) {
let dot_minecraft_path = dot_minecraft_path.as_ref().to_path_buf();
self.minecraft_path = dot_minecraft_path.to_string_lossy().to_string();
self.minecraft_library_path = dot_minecraft_path
.join("libraries")
.to_string_lossy()
.to_string();
self.minecraft_version_path = dot_minecraft_path
.join("versions")
.to_string_lossy()
.to_string();
self.minecraft_assets_path = dot_minecraft_path
.join("assets")
.to_string_lossy()
.to_string();
}
pub fn with_minecraft_path(mut self, dot_minecraft_path: impl AsRef<Path>) -> Self {
self.set_minecraft_path(dot_minecraft_path);
self
}
}
impl<R: Reporter> Clone for Downloader<R> {
fn clone(&self) -> Self {
Self {
source: self.source.clone(),
minecraft_path: self.minecraft_path.clone(),
minecraft_library_path: self.minecraft_library_path.clone(),
minecraft_version_path: self.minecraft_version_path.clone(),
minecraft_assets_path: self.minecraft_assets_path.clone(),
game_independent: self.game_independent,
verify_data: self.verify_data,
java_path: self.java_path.clone(),
parallel_lock: if self.parallel_amount == 0 {
inner_future::lock::Semaphore::new(usize::MAX)
} else {
inner_future::lock::Semaphore::new(self.parallel_amount)
},
reporter: self.reporter.clone(),
parallel_amount: self.parallel_amount,
}
}
}
impl<R: Reporter> Downloader<R> {
#[must_use]
pub fn with_reporter(mut self, reporter: R) -> Self {
self.reporter = Some(reporter);
self
}
#[must_use]
pub fn with_source(mut self, source: DownloadSource) -> Self {
self.source = source;
self
}
#[must_use]
pub fn with_java(mut self, java_path: String) -> Self {
self.java_path = java_path;
self
}
#[must_use]
pub fn with_game_independent(mut self, game_independent: bool) -> Self {
self.game_independent = game_independent;
self
}
#[must_use]
pub fn with_parallel_amount(mut self, limit: usize) -> Self {
self.parallel_amount = limit;
if limit == 0 {
self.parallel_lock = inner_future::lock::Semaphore::new(usize::MAX);
} else {
self.parallel_lock = inner_future::lock::Semaphore::new(limit);
}
self
}
#[must_use]
pub fn with_verify_data(mut self) -> Self {
self.verify_data = true;
self
}
}
impl<R: Reporter> Default for Downloader<R> {
fn default() -> Self {
Self {
source: DownloadSource::Default,
minecraft_path: MINECRAFT_PATH.to_owned(),
minecraft_library_path: MINECRAFT_LIBRARIES_PATH.to_owned(),
minecraft_version_path: MINECRAFT_VERSIONS_PATH.to_owned(),
minecraft_assets_path: MINECRAFT_ASSETS_PATH.to_owned(),
game_independent: false,
verify_data: false,
java_path: {
#[cfg(windows)]
{
"javaw.exe".into()
}
#[cfg(not(windows))]
{
"java".into()
}
},
reporter: None,
parallel_amount: 64,
parallel_lock: inner_future::lock::Semaphore::new(64),
}
}
}
#[async_trait]
pub trait GameDownload<'a>:
FabricDownloadExt + ForgeDownloadExt + VanillaDownloadExt + QuiltMCDownloadExt
{
async fn download_game(
&self,
version_name: &str,
vanilla: VersionInfo,
fabric: &str,
quiltmc: &str,
forge: &str,
optifine: &str,
) -> DynResult;
}
#[async_trait]
impl<R: Reporter> GameDownload<'_> for Downloader<R> {
async fn download_game(
&self,
version_name: &str,
vanilla: VersionInfo,
fabric: &str,
quiltmc: &str,
forge: &str,
optifine: &str,
) -> DynResult {
self.reporter
.set_message(format!("正在下载游戏 {version_name}"));
let launcher_profiles_path =
std::path::Path::new(&self.minecraft_path).join("launcher_profiles.json");
if !launcher_profiles_path.exists() {
inner_future::fs::create_dir_all(launcher_profiles_path.parent().unwrap()).await?;
inner_future::fs::write(launcher_profiles_path, r#"{"profiles":{},"selectedProfile":null,"authenticationDatabase":{},"selectedUser":{"account":"00000111112222233333444445555566","profile":"66666555554444433333222221111100"}}"#).await?;
}
if !fabric.is_empty() {
crate::prelude::inner_future::future::try_zip(
self.install_vanilla(version_name, &vanilla),
self.download_fabric_pre(version_name, &vanilla.id, fabric),
)
.await?;
self.download_fabric_post(version_name).await?;
} else if !quiltmc.is_empty() {
crate::prelude::inner_future::future::try_zip(
self.install_vanilla(version_name, &vanilla),
self.download_quiltmc_pre(version_name, &vanilla.id, quiltmc),
)
.await?;
self.download_quiltmc_post(version_name).await?;
} else if !forge.is_empty() {
self.install_vanilla(&vanilla.id, &vanilla).await?; crate::prelude::inner_future::future::try_zip(
self.install_vanilla(version_name, &vanilla),
self.install_forge_pre(version_name, &vanilla.id, forge),
)
.await?;
self.install_forge_post(version_name, &vanilla.id, forge)
.await?;
} else {
self.install_vanilla(version_name, &vanilla).await?;
}
if !optifine.is_empty() {
if forge.is_empty() && fabric.is_empty() {
self.install_vanilla(&vanilla.id, &vanilla).await?; }
let (optifine_type, optifine_patch) =
optifine.split_at(optifine.find(' ').context("Optifine 版本字符串不合法!")?);
self.install_optifine(
version_name,
&vanilla.id,
optifine_type,
&optifine_patch[1..],
!forge.is_empty() || !fabric.is_empty(),
)
.await?;
}
if !optifine.is_empty() || !forge.is_empty() {
let mut version_info = crate::version::structs::VersionInfo {
version_base: self.minecraft_version_path.to_owned(),
version: version_name.to_owned(),
..Default::default()
};
if version_info
.load()
.await
.context("无法读取安装完成后的版本元数据!")
.is_ok()
{
if let Some(meta) = &mut version_info.meta {
meta.fix_libraries();
self.download_libraries(&meta.libraries).await?;
}
}
}
Ok(())
}
}