diff options
Diffstat (limited to 'src/minecraft/downloads.rs')
| -rw-r--r-- | src/minecraft/downloads.rs | 172 |
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 @@ | |||
| 1 | use log::{debug, info}; | 1 | use log::{debug, info}; |
| 2 | use tokio::{fs, io::AsyncWriteExt}; | 2 | use reqwest::get; |
| 3 | use serde::Deserialize; | ||
| 4 | use tokio::{ | ||
| 5 | fs::{self, File, create_dir_all}, | ||
| 6 | io::AsyncWriteExt, | ||
| 7 | }; | ||
| 3 | 8 | ||
| 4 | use crate::{ | 9 | use 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)] |
| 17 | struct AssetObject { | ||
| 18 | hash: String, | ||
| 19 | size: u64, | ||
| 20 | } | ||
| 21 | |||
| 22 | #[derive(Debug, Deserialize)] | ||
| 23 | struct 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) |
| 14 | pub async fn download_all(config: &Config, version: &Version) -> Result<(), McError> { | 30 | /// - assets (w tym textures, sounds) |
| 31 | pub 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 | ||
| 20 | async fn download_client(config: &Config, version: &Version) -> Result<(), McError> { | 41 | async 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 | ||
| 33 | async fn download_libraries(config: &Config, libraries: &[Library]) -> Result<(), McError> { | 56 | async 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)?; | 89 | async 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() { | 129 | async 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 | 179 | async fn download_file( | |
| 54 | async 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(()) |
