minecraft_java_rs_core/loader/
quilt.rs1use tokio::sync::mpsc::Sender;
2
3use crate::error::LoaderError;
4use crate::launcher::events::LaunchEvent;
5use crate::launcher::options::LaunchOptions;
6use crate::models::loader::{FabricJson, QuiltMeta};
7use crate::models::minecraft::AssetItem;
8use crate::net::downloader::{DownloadItem, Downloader};
9use crate::net::http::fetch_json;
10use crate::utils::paths::get_path_libraries;
11
12use super::fabric::resolve_lib_url;
13
14const QUILT_META: &str = "https://meta.quiltmc.org/v3/versions";
15const QUILT_PROFILE: &str =
16 "https://meta.quiltmc.org/v3/versions/loader/${version}/${build}/profile/json";
17
18pub struct QuiltMC;
21
22impl QuiltMC {
23 pub fn new() -> Self {
24 Self
25 }
26
27 pub async fn download_json(
34 &self,
35 mc_version: &str,
36 build: &str,
37 client: &reqwest::Client,
38 ) -> Result<FabricJson, LoaderError> {
39 let meta: QuiltMeta = fetch_json(client, QUILT_META)
40 .await
41 .map_err(LoaderError::ApiError)?;
42
43 if !meta.game.iter().any(|g| g.version == mc_version) {
44 return Err(LoaderError::VersionNotFound(format!(
45 "QuiltMC doesn't support Minecraft {mc_version}"
46 )));
47 }
48
49 let build_ver = match build {
50 "latest" => meta
51 .loader
52 .first()
53 .map(|b| b.version.clone())
54 .ok_or_else(|| LoaderError::VersionNotFound("No Quilt builds available".into()))?,
55 "recommended" => meta
56 .loader
57 .iter()
58 .find(|b| !b.version.contains("beta"))
59 .map(|b| b.version.clone())
60 .ok_or_else(|| {
61 LoaderError::VersionNotFound("No stable Quilt build found".into())
62 })?,
63 ver => meta
64 .loader
65 .iter()
66 .find(|b| b.version == ver)
67 .map(|b| b.version.clone())
68 .ok_or_else(|| {
69 let available: Vec<_> =
70 meta.loader.iter().map(|b| b.version.as_str()).collect();
71 LoaderError::VersionNotFound(format!(
72 "Quilt build {ver} not found. Available: {}",
73 available.join(", ")
74 ))
75 })?,
76 };
77
78 let profile_url = QUILT_PROFILE
79 .replace("${version}", mc_version)
80 .replace("${build}", &build_ver);
81
82 let json: FabricJson = fetch_json(client, &profile_url)
83 .await
84 .map_err(LoaderError::ApiError)?;
85
86 Ok(json)
87 }
88
89 pub async fn download_libraries(
93 &self,
94 options: &LaunchOptions,
95 quilt_json: &FabricJson,
96 _client: &reqwest::Client,
97 event_tx: &Sender<LaunchEvent>,
98 ) -> Result<Vec<AssetItem>, LoaderError> {
99 let libs = &quilt_json.libraries;
100 let total = libs.len();
101 let mut items: Vec<AssetItem> = Vec::with_capacity(total);
102 let mut pending: Vec<DownloadItem> = Vec::new();
103
104 for (idx, lib) in libs.iter().enumerate() {
105 let _ = event_tx
106 .send(LaunchEvent::Check {
107 current: idx + 1,
108 total,
109 kind: "libraries".into(),
110 })
111 .await;
112
113 if lib.rules.is_some() {
114 continue;
115 }
116
117 let lib_info = match get_path_libraries(&lib.name, None, None) {
118 Ok(i) => i,
119 Err(_) => continue,
120 };
121
122 let folder = options
123 .loader_dir("quilt")
124 .join("libraries")
125 .join(&lib_info.path);
126 let dest = folder.join(&lib_info.name);
127 let url = resolve_lib_url(lib, &lib_info.path, &lib_info.name);
128
129 items.push(AssetItem::Asset {
130 path: dest.to_string_lossy().into_owned(),
131 sha1: lib
132 .downloads
133 .as_ref()
134 .and_then(|d| d.artifact.as_ref())
135 .and_then(|a| a.sha1.clone())
136 .unwrap_or_default(),
137 size: lib
138 .downloads
139 .as_ref()
140 .and_then(|d| d.artifact.as_ref())
141 .and_then(|a| a.size)
142 .unwrap_or(0),
143 url: url.clone(),
144 });
145
146 if !dest.exists() {
147 pending.push(DownloadItem {
148 url,
149 path: dest,
150 folder,
151 name: lib_info.name,
152 size: 0,
153 r#type: Some("libraries".into()),
154 sha1: None,
155 });
156 }
157 }
158
159 if !pending.is_empty() {
160 let downloader = Downloader::new(
161 options.timeout_secs,
162 options.clamped_concurrency(),
163 options.force_ipv4,
164 );
165 downloader
166 .download_multiple(pending, event_tx.clone())
167 .await
168 .map_err(|e| {
169 LoaderError::Io(std::io::Error::new(
170 std::io::ErrorKind::Other,
171 e.to_string(),
172 ))
173 })?;
174 }
175
176 Ok(items)
177 }
178}
179
180impl Default for QuiltMC {
181 fn default() -> Self {
182 Self::new()
183 }
184}
185
186#[cfg(test)]
189mod tests {
190 use super::*;
191
192 #[test]
193 fn quilt_mc_constructs_without_options() {
194 let _q = QuiltMC::new();
195 }
196
197 #[test]
198 fn quilt_mc_default_same_as_new() {
199 let _q = QuiltMC::default();
200 }
201}