hexomc_lib/install/
fabric.rs1use crate::{
2 download::{download_batch, DownloadTask},
3 error::Result,
4 install::vanilla::InstanceConfig,
5};
6use serde::{Deserialize, Serialize};
7use std::path::Path;
8
9const FABRIC_META_BASE: &str = "https://meta.fabricmc.net/v2";
10
11#[derive(Debug, Deserialize, Serialize, Clone)]
12pub struct FabricLoaderVersion {
13 pub version: String,
14 pub stable: bool,
15}
16
17#[derive(Debug, Deserialize, Serialize, Clone)]
18#[serde(rename_all = "camelCase")]
19pub struct FabricProfileJson {
20 pub main_class: String,
21 pub arguments: FabricArguments,
22 pub libraries: Vec<FabricLibrary>,
23}
24
25#[derive(Debug, Deserialize, Serialize, Clone)]
26pub struct FabricArguments {
27 pub jvm: Option<Vec<String>>,
28 pub game: Option<Vec<String>>,
29}
30
31#[derive(Debug, Deserialize, Serialize, Clone)]
32pub struct FabricLibrary {
33 pub name: String,
34 pub url: String,
35}
36
37pub async fn get_fabric_loader_versions(mc_version: &str) -> Result<Vec<FabricLoaderVersion>> {
38 let url = format!("{}/versions/loader/{}", FABRIC_META_BASE, mc_version);
39 let client = reqwest::Client::new();
40
41 #[derive(Deserialize)]
42 struct Entry {
43 loader: FabricLoaderVersion,
44 }
45
46 let entries: Vec<Entry> = client.get(&url).send().await?.json().await?;
47 Ok(entries.into_iter().map(|e| e.loader).collect())
48}
49
50async fn fetch_fabric_profile(mc_version: &str, loader_version: &str) -> Result<FabricProfileJson> {
51 let url = format!(
52 "{}/versions/loader/{}/{}/profile/json",
53 FABRIC_META_BASE, mc_version, loader_version
54 );
55 let client = reqwest::Client::new();
56 let profile = client
57 .get(&url)
58 .send()
59 .await?
60 .json::<FabricProfileJson>()
61 .await?;
62 Ok(profile)
63}
64
65pub async fn install_fabric(
66 mc_version: &str,
67 loader_version: &str,
68 instance_name: &str,
69 base_dir: &Path,
70) -> Result<()> {
71 let instance_dir = base_dir.join("instance").join(instance_name);
72 let lib_dir = base_dir.join("libraries");
73
74 let mut config = InstanceConfig::load(&instance_dir).await?;
75 let profile = fetch_fabric_profile(mc_version, loader_version).await?;
76
77 let mut tasks: Vec<DownloadTask> = Vec::new();
78 let mut new_libs = Vec::new();
79
80 for fab_lib in &profile.libraries {
81 let parsed = parse_fabric_lib(fab_lib)?;
82
83 let artifact_path = lib_dir.join(&parsed.local_path);
84 let sha1 = fetch_text(&parsed.sha1_url).await.unwrap_or_default();
85
86 tasks.push(
87 DownloadTask::new(&parsed.jar_url, &artifact_path).with_sha1(sha1.trim().to_string()),
88 );
89
90 new_libs.push(crate::install::vanilla::LibEntry {
91 name: fab_lib.name.clone(),
92 path: artifact_path,
93 sha1: sha1.trim().to_string(),
94 });
95 }
96
97 download_batch(tasks, 8, |_, _| {}).await?;
98
99 if let Some(jvm_args) = &profile.arguments.jvm {
101 let trimmed: Vec<String> = jvm_args.iter().map(|s| s.trim().to_string()).collect();
102 let mut new_args = trimmed;
103 new_args.extend(config.start_args.drain(..));
104 config.start_args = new_args;
105 }
106
107 if let Some(game_args) = &profile.arguments.game {
108 config.start_args.extend(game_args.iter().cloned());
109 }
110
111 config.main_class = profile.main_class.clone();
112 config.loader_type = crate::install::vanilla::LoaderType::Fabric;
113
114 new_libs.extend(config.lib_list.drain(..));
116 config.lib_list = new_libs;
117
118 config.save(&instance_dir).await?;
119 Ok(())
120}
121
122struct FabricLibParsed {
123 jar_url: String,
124 sha1_url: String,
125 local_path: std::path::PathBuf,
126}
127
128fn parse_fabric_lib(lib: &FabricLibrary) -> Result<FabricLibParsed> {
129 let parts: Vec<&str> = lib.name.split(':').collect();
130 let group = parts[0].replace('.', "/");
131 let artifact = parts[1];
132 let version = parts[2];
133
134 let base = format!("{}{}/{}/{}", lib.url, group, artifact, version);
135 let jar_name = format!("{}-{}.jar", artifact, version);
136 let jar_url = format!("{}/{}", base, jar_name);
137 let sha1_url = format!("{}/{}.sha1", base, jar_name);
138
139 let local_path = std::path::PathBuf::from(&group)
140 .join(artifact)
141 .join(version)
142 .join(&jar_name);
143
144 Ok(FabricLibParsed {
145 jar_url,
146 sha1_url,
147 local_path,
148 })
149}
150
151async fn fetch_text(url: &str) -> Result<String> {
152 let client = reqwest::Client::new();
153 let text = client.get(url).send().await?.text().await?;
154 Ok(text)
155}