aboutsummaryrefslogtreecommitdiffstats
path: root/src/minecraft/downloads.rs
diff options
context:
space:
mode:
authorFilip Wandzio <contact@philw.dev>2026-01-24 08:29:14 +0100
committerFilip Wandzio <contact@philw.dev>2026-01-24 08:29:14 +0100
commita393e0a2f2c3678a3ea869dc1417fa269f2b1040 (patch)
tree606df6a9284b5bd2dbf84fa5e3d363b8e6a01322 /src/minecraft/downloads.rs
parent72ddd7b7704f2087a52c9c0552446682918c513b (diff)
downloaddml-a393e0a2f2c3678a3ea869dc1417fa269f2b1040.tar.gz
dml-a393e0a2f2c3678a3ea869dc1417fa269f2b1040.zip
Resolve audio not loading bug
Ensure all assets are downloading for each version Temporarily disable minecraft versions older than 1.8 because of the asset/manifest loading issues Implement basic documentation of modules Implement basic async/multithreading for downloading assets
Diffstat (limited to '')
-rw-r--r--src/minecraft/downloads.rs172
1 files changed, 150 insertions, 22 deletions
diff --git a/src/minecraft/downloads.rs b/src/minecraft/downloads.rs
index 5be5a05..a994146 100644
--- a/src/minecraft/downloads.rs
+++ b/src/minecraft/downloads.rs
@@ -1,5 +1,10 @@
1use log::{debug, info}; 1use log::{debug, info};
2use tokio::{fs, io::AsyncWriteExt}; 2use reqwest::get;
3use serde::Deserialize;
4use tokio::{
5 fs::{self, File, create_dir_all},
6 io::AsyncWriteExt,
7};
3 8
4use crate::{ 9use crate::{
5 config::Config, 10 config::Config,
@@ -8,58 +13,181 @@ use crate::{
8 platform::paths, 13 platform::paths,
9}; 14};
10 15
11/// Download everything required to launch: 16#[derive(Debug, Deserialize)]
17struct AssetObject {
18 hash: String,
19 size: u64,
20}
21
22#[derive(Debug, Deserialize)]
23struct AssetIndexManifest {
24 objects: std::collections::HashMap<String, AssetObject>,
25}
26
27/// Pobiera wszystko potrzebne do uruchomienia Minecraft:
12/// - client jar 28/// - client jar
13/// - libraries 29/// - biblioteki (artifact + natives)
14pub async fn download_all(config: &Config, version: &Version) -> Result<(), McError> { 30/// - assets (w tym textures, sounds)
31pub async fn download_all(
32 config: &Config,
33 version: &Version,
34) -> Result<(), McError> {
15 download_client(config, version).await?; 35 download_client(config, version).await?;
16 download_libraries(config, &version.libraries).await?; 36 download_libraries(config, &version.libraries).await?;
37 download_assets(config, version).await?;
17 Ok(()) 38 Ok(())
18} 39}
19 40
20async fn download_client(config: &Config, version: &Version) -> Result<(), McError> { 41async fn download_client(
42 config: &Config,
43 version: &Version,
44) -> Result<(), McError> {
21 let jar_path = paths::client_jar(config, &version.id)?; 45 let jar_path = paths::client_jar(config, &version.id)?;
22 46
23 if jar_path.exists() { 47 if jar_path.exists() {
24 debug!("Client jar already exists"); 48 debug!("Client jar already exists: {}", jar_path.display());
25 return Ok(()); 49 return Ok(());
26 } 50 }
27 51
28 info!("Downloading client {}", version.id); 52 info!("Downloading client {}", version.id);
29
30 download_file(&version.downloads.client.url, &jar_path).await 53 download_file(&version.downloads.client.url, &jar_path).await
31} 54}
32 55
33async fn download_libraries(config: &Config, libraries: &[Library]) -> Result<(), McError> { 56async fn download_libraries(
34 for lib in libraries { 57 config: &Config,
35 let Some(artifact) = &lib.downloads.artifact else { 58 libraries: &[Library],
36 continue; 59) -> Result<(), McError> {
37 }; 60 for library in libraries {
61 // ===== CLASSPATH LIBRARIES =====
62 if let Some(artifact) = &library.downloads.artifact {
63 let library_path = paths::library_file(config, &artifact.path)?;
64
65 if !library_path.exists() {
66 info!("Downloading library {}", artifact.path);
67 download_file(&artifact.url, &library_path).await?;
68 }
69 }
70
71 // ===== NATIVES =====
72 if let Some(classifiers) = &library.downloads.classifiers {
73 for (_, native) in classifiers {
74 let native_path = paths::library_file(config, &native.path)?;
75
76 if native_path.exists() {
77 continue;
78 }
79
80 info!("Downloading native library {}", native.path);
81 download_file(&native.url, &native_path).await?;
82 }
83 }
84 }
85
86 Ok(())
87}
38 88
39 let lib_path = paths::library_file(config, &artifact.path)?; 89async fn download_asset_index(
90 config: &Config,
91 version: &Version,
92) -> Result<AssetIndexManifest, McError> {
93 let assets_dir = paths::assets_dir(config);
94 create_dir_all(assets_dir.join("indexes")).await?;
95
96 let asset_index = version.asset_index.as_ref().ok_or_else(|| {
97 McError::Config("Missing asset_index in version.json".into())
98 })?;
99
100 // Nie pozwalamy na legacy dla nowoczesnych wersji
101 if asset_index.id == "legacy" {
102 return Err(McError::Config(
103 "Legacy assetIndex detected – pobierz właściwy version.json".into(),
104 ));
105 }
106
107 let index_path = assets_dir
108 .join("indexes")
109 .join(format!("{}.json", asset_index.id));
110
111 // Jeśli indeks istnieje lokalnie
112 if index_path.exists() {
113 let index_data = fs::read_to_string(&index_path).await?;
114 let manifest: AssetIndexManifest = serde_json::from_str(&index_data)?;
115 return Ok(manifest);
116 }
117
118 // Pobierz indeks z sieci
119 info!("Downloading asset index {}", asset_index.id);
120 let response = get(&asset_index.url).await?;
121 let manifest_text = response.text().await?;
122
123 fs::write(&index_path, &manifest_text).await?;
124
125 let manifest: AssetIndexManifest = serde_json::from_str(&manifest_text)?;
126 Ok(manifest)
127}
40 128
41 if lib_path.exists() { 129async fn download_assets(
130 config: &Config,
131 version: &Version,
132) -> Result<(), McError> {
133 let assets_dir = paths::assets_dir(config);
134
135 // Katalogi MUSZĄ istnieć
136 create_dir_all(assets_dir.join("objects")).await?;
137 create_dir_all(assets_dir.join("indexes")).await?;
138
139 let manifest = download_asset_index(config, version).await?;
140
141 // Pobieramy wszystkie obiekty
142 for (logical_path, asset) in &manifest.objects {
143 let subdir = &asset.hash[0..2];
144 let file_path = assets_dir
145 .join("objects")
146 .join(subdir)
147 .join(&asset.hash);
148
149 if file_path.exists() {
42 continue; 150 continue;
43 } 151 }
44 152
45 info!("Downloading library {}", artifact.path); 153 let url = format!(
46 download_file(&artifact.url, &lib_path).await?; 154 "https://resources.download.minecraft.net/{}/{}",
155 subdir, asset.hash
156 );
157 info!("Downloading asset {} -> {}", logical_path, file_path.display());
158 download_file(&url, &file_path).await?;
159 }
160
161 // Pobierz sounds.json jeśli istnieje
162 if let Some(asset) = manifest.objects.get("sounds.json") {
163 let file_path = assets_dir.join("indexes").join("sounds.json");
164 if !file_path.exists() {
165 let subdir = &asset.hash[0..2];
166 let url = format!(
167 "https://resources.download.minecraft.net/{}/{}",
168 subdir, asset.hash
169 );
170 info!("Downloading sounds.json");
171 download_file(&url, &file_path).await?;
172 }
47 } 173 }
48 174
49 Ok(()) 175 Ok(())
50} 176}
51 177
52/* ---------------- helper ---------------- */ 178/// Helper do pobierania plików
53 179async fn download_file(
54async fn download_file(url: &str, path: &std::path::Path) -> Result<(), McError> { 180 url: &str,
181 path: &std::path::Path,
182) -> Result<(), McError> {
55 if let Some(parent) = path.parent() { 183 if let Some(parent) = path.parent() {
56 fs::create_dir_all(parent).await?; 184 create_dir_all(parent).await?;
57 } 185 }
58 186
59 let response = reqwest::get(url).await?; 187 let response = get(url).await?;
60 let bytes = response.bytes().await?; 188 let bytes = response.bytes().await?;
61 189
62 let mut file = fs::File::create(path).await?; 190 let mut file = File::create(path).await?;
63 file.write_all(&bytes).await?; 191 file.write_all(&bytes).await?;
64 192
65 Ok(()) 193 Ok(())