1pub mod authlib;
4pub mod curseforge;
5pub mod fabric;
6pub mod forge;
7pub mod mcmod;
8pub mod modrinth;
9pub mod optifine;
10pub mod quiltmc;
11pub mod structs;
12pub mod vanilla;
13
14use std::{fmt::Display, path::Path, str::FromStr};
15
16use anyhow::Context;
17use async_trait::async_trait;
18pub use authlib::AuthlibDownloadExt;
19pub use fabric::FabricDownloadExt;
20pub use forge::ForgeDownloadExt;
21pub use optifine::OptifineDownloadExt;
22pub use quiltmc::QuiltMCDownloadExt;
23use serde::{Deserialize, Serialize};
24pub use vanilla::VanillaDownloadExt;
25
26use self::structs::VersionInfo;
27use crate::{path::*, prelude::*, progress::*};
28
29#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
33pub enum DownloadSource {
34 Default,
36 BMCLAPI,
40 MCBBS,
42 Custom(url::Url),
44}
45
46impl Default for DownloadSource {
47 fn default() -> Self {
48 Self::Default
49 }
50}
51
52impl Display for DownloadSource {
53 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54 write!(
55 f,
56 "{}",
57 match self {
58 DownloadSource::Default => "默认(官方)下载源",
59 DownloadSource::BMCLAPI => "BMCLAPI 下载源",
60 DownloadSource::MCBBS => "MCBBS 下载源",
61 DownloadSource::Custom(_) => "自定义",
62 }
63 )
64 }
65}
66
67impl FromStr for DownloadSource {
68 type Err = ();
69
70 fn from_str(s: &str) -> Result<Self, Self::Err> {
71 match s {
72 "Offical" => Ok(Self::Default),
73 "BMCLAPI" => Ok(Self::BMCLAPI),
74 "MCBBS" => Ok(Self::MCBBS),
75 s => {
76 let url = s.parse::<url::Url>();
77 if let Ok(url) = url {
78 Ok(Self::Custom(url))
79 } else {
80 Ok(Self::Default)
81 }
82 }
83 }
84 }
85}
86
87#[derive(Debug)]
89pub struct Downloader<R> {
90 pub source: DownloadSource,
92 pub(crate) minecraft_path: String,
94 pub(crate) minecraft_library_path: String,
96 pub(crate) minecraft_version_path: String,
98 pub(crate) minecraft_assets_path: String,
100 pub game_independent: bool,
106 pub verify_data: bool,
108 pub java_path: String,
112 parallel_amount: usize,
114 pub(crate) parallel_lock: inner_future::lock::Semaphore,
116 pub reporter: Option<R>,
118}
119
120impl<R> Downloader<R> {
123 pub fn set_minecraft_path(&mut self, dot_minecraft_path: impl AsRef<Path>) {
127 let dot_minecraft_path = dot_minecraft_path.as_ref().to_path_buf();
128 self.minecraft_path = dot_minecraft_path.to_string_lossy().to_string();
129 self.minecraft_library_path = dot_minecraft_path
130 .join("libraries")
131 .to_string_lossy()
132 .to_string();
133 self.minecraft_version_path = dot_minecraft_path
134 .join("versions")
135 .to_string_lossy()
136 .to_string();
137 self.minecraft_assets_path = dot_minecraft_path
138 .join("assets")
139 .to_string_lossy()
140 .to_string();
141 }
142
143 pub fn with_minecraft_path(mut self, dot_minecraft_path: impl AsRef<Path>) -> Self {
145 self.set_minecraft_path(dot_minecraft_path);
146 self
147 }
148}
149
150impl<R: Reporter> Clone for Downloader<R> {
151 fn clone(&self) -> Self {
152 Self {
153 source: self.source.clone(),
154 minecraft_path: self.minecraft_path.clone(),
155 minecraft_library_path: self.minecraft_library_path.clone(),
156 minecraft_version_path: self.minecraft_version_path.clone(),
157 minecraft_assets_path: self.minecraft_assets_path.clone(),
158 game_independent: self.game_independent,
159 verify_data: self.verify_data,
160 java_path: self.java_path.clone(),
161 parallel_lock: if self.parallel_amount == 0 {
162 inner_future::lock::Semaphore::new(usize::MAX)
163 } else {
164 inner_future::lock::Semaphore::new(self.parallel_amount)
165 },
166 reporter: self.reporter.clone(),
167 parallel_amount: self.parallel_amount,
168 }
169 }
170}
171
172impl<R: Reporter> Downloader<R> {
173 #[must_use]
175 pub fn with_reporter(mut self, reporter: R) -> Self {
176 self.reporter = Some(reporter);
177 self
178 }
179 #[must_use]
181 pub fn with_source(mut self, source: DownloadSource) -> Self {
182 self.source = source;
183 self
184 }
185 #[must_use]
187 pub fn with_java(mut self, java_path: String) -> Self {
188 self.java_path = java_path;
189 self
190 }
191 #[must_use]
197 pub fn with_game_independent(mut self, game_independent: bool) -> Self {
198 self.game_independent = game_independent;
199 self
200 }
201 #[must_use]
203 pub fn with_parallel_amount(mut self, limit: usize) -> Self {
204 self.parallel_amount = limit;
205 if limit == 0 {
206 self.parallel_lock = inner_future::lock::Semaphore::new(usize::MAX);
207 } else {
208 self.parallel_lock = inner_future::lock::Semaphore::new(limit);
209 }
210 self
211 }
212 #[must_use]
216 pub fn with_verify_data(mut self) -> Self {
217 self.verify_data = true;
218 self
219 }
220}
221impl<R: Reporter> Default for Downloader<R> {
222 fn default() -> Self {
223 Self {
224 source: DownloadSource::Default,
225 minecraft_path: MINECRAFT_PATH.to_owned(),
226 minecraft_library_path: MINECRAFT_LIBRARIES_PATH.to_owned(),
227 minecraft_version_path: MINECRAFT_VERSIONS_PATH.to_owned(),
228 minecraft_assets_path: MINECRAFT_ASSETS_PATH.to_owned(),
229 game_independent: false,
230 verify_data: false,
231 java_path: {
232 #[cfg(windows)]
233 {
234 "javaw.exe".into()
235 }
236 #[cfg(not(windows))]
237 {
238 "java".into()
239 }
240 },
241 reporter: None,
242 parallel_amount: 64,
243 parallel_lock: inner_future::lock::Semaphore::new(64),
244 }
245 }
246}
247
248#[async_trait]
250pub trait GameDownload<'a>:
251 FabricDownloadExt + ForgeDownloadExt + VanillaDownloadExt + QuiltMCDownloadExt
252{
253 async fn download_game(
255 &self,
256 version_name: &str,
257 vanilla: VersionInfo,
258 fabric: &str,
259 quiltmc: &str,
260 forge: &str,
261 optifine: &str,
262 ) -> DynResult;
263}
264
265#[async_trait]
266impl<R: Reporter> GameDownload<'_> for Downloader<R> {
267 async fn download_game(
268 &self,
269 version_name: &str,
270 vanilla: VersionInfo,
271 fabric: &str,
272 quiltmc: &str,
273 forge: &str,
274 optifine: &str,
275 ) -> DynResult {
276 self.reporter
277 .set_message(format!("正在下载游戏 {version_name}"));
278
279 let launcher_profiles_path =
280 std::path::Path::new(&self.minecraft_path).join("launcher_profiles.json");
281
282 if !launcher_profiles_path.exists() {
283 inner_future::fs::create_dir_all(launcher_profiles_path.parent().unwrap()).await?;
284 inner_future::fs::write(launcher_profiles_path, r#"{"profiles":{},"selectedProfile":null,"authenticationDatabase":{},"selectedUser":{"account":"00000111112222233333444445555566","profile":"66666555554444433333222221111100"}}"#).await?;
285 }
286
287 if !fabric.is_empty() {
288 crate::prelude::inner_future::future::try_zip(
289 self.install_vanilla(version_name, &vanilla),
290 self.download_fabric_pre(version_name, &vanilla.id, fabric),
291 )
292 .await?;
293 self.download_fabric_post(version_name).await?;
294 } else if !quiltmc.is_empty() {
295 crate::prelude::inner_future::future::try_zip(
296 self.install_vanilla(version_name, &vanilla),
297 self.download_quiltmc_pre(version_name, &vanilla.id, quiltmc),
298 )
299 .await?;
300 self.download_quiltmc_post(version_name).await?;
301 } else if !forge.is_empty() {
302 self.install_vanilla(&vanilla.id, &vanilla).await?; crate::prelude::inner_future::future::try_zip(
304 self.install_vanilla(version_name, &vanilla),
305 self.install_forge_pre(version_name, &vanilla.id, forge),
306 )
307 .await?;
308 self.install_forge_post(version_name, &vanilla.id, forge)
309 .await?;
310 } else {
311 self.install_vanilla(version_name, &vanilla).await?;
312 }
313 if !optifine.is_empty() {
314 if forge.is_empty() && fabric.is_empty() {
315 self.install_vanilla(&vanilla.id, &vanilla).await?; }
317 let (optifine_type, optifine_patch) =
318 optifine.split_at(optifine.find(' ').context("Optifine 版本字符串不合法!")?);
319 self.install_optifine(
320 version_name,
321 &vanilla.id,
322 optifine_type,
323 &optifine_patch[1..],
324 !forge.is_empty() || !fabric.is_empty(),
325 )
326 .await?;
327 }
328
329 if !optifine.is_empty() || !forge.is_empty() {
332 let mut version_info = crate::version::structs::VersionInfo {
333 version_base: self.minecraft_version_path.to_owned(),
334 version: version_name.to_owned(),
335 ..Default::default()
336 };
337
338 if version_info
339 .load()
340 .await
341 .context("无法读取安装完成后的版本元数据!")
342 .is_ok()
343 {
344 if let Some(meta) = &mut version_info.meta {
345 meta.fix_libraries();
346 self.download_libraries(&meta.libraries).await?;
347 }
348 }
349 }
350 Ok(())
351 }
352}