diff options
| author | Filip Wandzio <contact@philw.dev> | 2026-01-24 08:29:14 +0100 |
|---|---|---|
| committer | Filip Wandzio <contact@philw.dev> | 2026-01-24 08:29:14 +0100 |
| commit | a393e0a2f2c3678a3ea869dc1417fa269f2b1040 (patch) | |
| tree | 606df6a9284b5bd2dbf84fa5e3d363b8e6a01322 /src/minecraft | |
| parent | 72ddd7b7704f2087a52c9c0552446682918c513b (diff) | |
| download | dml-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.rs | 172 | ||||
| -rw-r--r-- | src/minecraft/extraction.rs | 116 | ||||
| -rw-r--r-- | src/minecraft/launcher.rs | 91 | ||||
| -rw-r--r-- | src/minecraft/manifests.rs | 43 | ||||
| -rw-r--r-- | src/minecraft/mod.rs | 15 |
5 files changed, 391 insertions, 46 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(()) |
diff --git a/src/minecraft/extraction.rs b/src/minecraft/extraction.rs index 5175ee0..b58fd2e 100644 --- a/src/minecraft/extraction.rs +++ b/src/minecraft/extraction.rs | |||
| @@ -1,10 +1,118 @@ | |||
| 1 | use std::{fs, io, path::Path}; | ||
| 2 | |||
| 1 | use log::info; | 3 | use log::info; |
| 4 | use zip::ZipArchive; | ||
| 5 | |||
| 6 | use crate::{ | ||
| 7 | errors::McError, | ||
| 8 | minecraft::manifests::{Library, Version}, | ||
| 9 | }; | ||
| 2 | 10 | ||
| 3 | use crate::errors::McError; | ||
| 4 | pub fn extract_natives( | 11 | pub fn extract_natives( |
| 5 | _cfg: &crate::config::Config, | 12 | cfg: &crate::config::Config, |
| 6 | version: &crate::minecraft::manifests::Version, | 13 | version: &Version, |
| 7 | ) -> Result<(), McError> { | 14 | ) -> Result<(), McError> { |
| 8 | info!("Extracting natives for {}", version.id); | 15 | let natives_dir = cfg |
| 16 | .data_dir | ||
| 17 | .join("minecraft") | ||
| 18 | .join("versions") | ||
| 19 | .join(&version.id) | ||
| 20 | .join("natives"); | ||
| 21 | |||
| 22 | info!("Extracting natives for {} into {:?}", version.id, natives_dir); | ||
| 23 | |||
| 24 | if natives_dir.exists() { | ||
| 25 | fs::remove_dir_all(&natives_dir)?; | ||
| 26 | } | ||
| 27 | fs::create_dir_all(&natives_dir)?; | ||
| 28 | |||
| 29 | for lib in &version.libraries { | ||
| 30 | if !library_allowed(lib) { | ||
| 31 | continue; | ||
| 32 | } | ||
| 33 | |||
| 34 | let natives = match &lib.natives { | ||
| 35 | | Some(n) => n, | ||
| 36 | | None => continue, | ||
| 37 | }; | ||
| 38 | |||
| 39 | let classifier = match natives.get("linux") { | ||
| 40 | | Some(c) => c, | ||
| 41 | | None => continue, | ||
| 42 | }; | ||
| 43 | |||
| 44 | let classifiers = match &lib.downloads.classifiers { | ||
| 45 | | Some(c) => c, | ||
| 46 | | None => continue, | ||
| 47 | }; | ||
| 48 | |||
| 49 | let artifact = match classifiers.get(classifier) { | ||
| 50 | | Some(a) => a, | ||
| 51 | | None => continue, | ||
| 52 | }; | ||
| 53 | |||
| 54 | let jar_path = cfg | ||
| 55 | .data_dir | ||
| 56 | .join("minecraft") | ||
| 57 | .join("libraries") | ||
| 58 | .join(&artifact.path); | ||
| 59 | |||
| 60 | info!("Extracting natives from {:?}", jar_path); | ||
| 61 | |||
| 62 | extract_zip(&jar_path, &natives_dir)?; | ||
| 63 | } | ||
| 64 | |||
| 65 | Ok(()) | ||
| 66 | } | ||
| 67 | |||
| 68 | fn library_allowed(lib: &Library) -> bool { | ||
| 69 | let rules = match &lib.rules { | ||
| 70 | | Some(r) => r, | ||
| 71 | | None => return true, | ||
| 72 | }; | ||
| 73 | |||
| 74 | let mut allowed = false; | ||
| 75 | |||
| 76 | for rule in rules { | ||
| 77 | let os_match = match &rule.os { | ||
| 78 | | Some(os) => os.name == "linux", | ||
| 79 | | None => true, | ||
| 80 | }; | ||
| 81 | |||
| 82 | if os_match { | ||
| 83 | allowed = rule.action == "allow"; | ||
| 84 | } | ||
| 85 | } | ||
| 86 | |||
| 87 | allowed | ||
| 88 | } | ||
| 89 | |||
| 90 | fn extract_zip(jar_path: &Path, out_dir: &Path) -> Result<(), McError> { | ||
| 91 | let file = fs::File::open(jar_path)?; | ||
| 92 | let mut zip = ZipArchive::new(file)?; | ||
| 93 | |||
| 94 | for i in 0..zip.len() { | ||
| 95 | let mut entry = zip.by_index(i)?; | ||
| 96 | let name = entry.name(); | ||
| 97 | |||
| 98 | if name.starts_with("META-INF/") { | ||
| 99 | continue; | ||
| 100 | } | ||
| 101 | |||
| 102 | let out_path = out_dir.join(name); | ||
| 103 | |||
| 104 | if entry.is_dir() { | ||
| 105 | fs::create_dir_all(&out_path)?; | ||
| 106 | continue; | ||
| 107 | } | ||
| 108 | |||
| 109 | if let Some(parent) = out_path.parent() { | ||
| 110 | fs::create_dir_all(parent)?; | ||
| 111 | } | ||
| 112 | |||
| 113 | let mut out_file = fs::File::create(&out_path)?; | ||
| 114 | io::copy(&mut entry, &mut out_file)?; | ||
| 115 | } | ||
| 116 | |||
| 9 | Ok(()) | 117 | Ok(()) |
| 10 | } | 118 | } |
diff --git a/src/minecraft/launcher.rs b/src/minecraft/launcher.rs index f7e3ecc..cfd6c85 100644 --- a/src/minecraft/launcher.rs +++ b/src/minecraft/launcher.rs | |||
| @@ -2,26 +2,39 @@ use std::process::Command; | |||
| 2 | 2 | ||
| 3 | use log::{debug, info}; | 3 | use log::{debug, info}; |
| 4 | 4 | ||
| 5 | use crate::{config::Config, errors::McError, minecraft::manifests::Version, platform::paths}; | 5 | use crate::{ |
| 6 | config::Config, | ||
| 7 | errors::McError, | ||
| 8 | minecraft::manifests::{Library, Version}, | ||
| 9 | platform::paths, | ||
| 10 | }; | ||
| 6 | 11 | ||
| 7 | /// Build the full classpath | 12 | /// Buduje classpath dla danej wersji Minecrafta |
| 8 | fn build_classpath(config: &Config, version: &Version) -> Result<String, McError> { | 13 | fn build_classpath( |
| 14 | config: &Config, | ||
| 15 | version: &Version, | ||
| 16 | ) -> Result<String, McError> { | ||
| 9 | let sep = if cfg!(windows) { ";" } else { ":" }; | 17 | let sep = if cfg!(windows) { ";" } else { ":" }; |
| 10 | let mut entries = Vec::new(); | 18 | let mut entries = Vec::new(); |
| 11 | 19 | ||
| 12 | for lib in &version.libraries { | 20 | for library in &version.libraries { |
| 13 | if let Some(artifact) = &lib.downloads.artifact { | 21 | if !library_allowed(library) { |
| 22 | continue; | ||
| 23 | } | ||
| 24 | if let Some(artifact) = &library.downloads.artifact { | ||
| 14 | let path = paths::library_file(config, &artifact.path)?; | 25 | let path = paths::library_file(config, &artifact.path)?; |
| 15 | entries.push(path.to_string_lossy().to_string()); | 26 | entries.push(path.to_string_lossy().to_string()); |
| 16 | } | 27 | } |
| 17 | } | 28 | } |
| 18 | 29 | ||
| 30 | // client.jar zawsze na końcu classpath | ||
| 19 | let client_jar = paths::client_jar(config, &version.id)?; | 31 | let client_jar = paths::client_jar(config, &version.id)?; |
| 20 | entries.push(client_jar.to_string_lossy().to_string()); | 32 | entries.push(client_jar.to_string_lossy().to_string()); |
| 33 | |||
| 21 | Ok(entries.join(sep)) | 34 | Ok(entries.join(sep)) |
| 22 | } | 35 | } |
| 23 | 36 | ||
| 24 | /// Launch Minecraft | 37 | /// Uruchamia Minecraft |
| 25 | pub fn launch(config: &Config, version: &Version) -> Result<(), McError> { | 38 | pub fn launch(config: &Config, version: &Version) -> Result<(), McError> { |
| 26 | let java = &config.java_path; | 39 | let java = &config.java_path; |
| 27 | let classpath = build_classpath(config, version)?; | 40 | let classpath = build_classpath(config, version)?; |
| @@ -34,26 +47,46 @@ pub fn launch(config: &Config, version: &Version) -> Result<(), McError> { | |||
| 34 | ))); | 47 | ))); |
| 35 | } | 48 | } |
| 36 | 49 | ||
| 50 | let asset_index_id = version | ||
| 51 | .asset_index | ||
| 52 | .as_ref() | ||
| 53 | .ok_or_else(|| { | ||
| 54 | McError::Runtime("Missing assetIndex in version.json".into()) | ||
| 55 | })? | ||
| 56 | .id | ||
| 57 | .clone(); | ||
| 58 | |||
| 37 | info!("Launching Minecraft {}", version.id); | 59 | info!("Launching Minecraft {}", version.id); |
| 38 | debug!("Classpath: {}", classpath); | 60 | debug!("Classpath: {}", classpath); |
| 39 | debug!("Natives: {}", natives_dir.display()); | 61 | debug!("Natives: {}", natives_dir.display()); |
| 62 | debug!("Asset index: {}", asset_index_id); | ||
| 63 | |||
| 64 | let mut cmd = Command::new(java); | ||
| 65 | |||
| 66 | // ===== JVM ARGUMENTS (muszą być na początku) ===== | ||
| 67 | cmd.arg(format!("-Xmx{}M", config.max_memory_mb)) | ||
| 68 | .arg(format!("-Djava.library.path={}", natives_dir.display())); | ||
| 40 | 69 | ||
| 41 | let status = Command::new(java) | 70 | for arg in &config.jvm_args { |
| 42 | .arg(format!("-Xmx{}M", config.max_memory_mb)) | 71 | cmd.arg(arg); |
| 43 | .arg(format!("-Djava.library.path={}", natives_dir.display())) | 72 | } |
| 44 | .arg("-cp") | 73 | |
| 74 | // ===== CLASSPATH + MAIN CLASS ===== | ||
| 75 | cmd.arg("-cp") | ||
| 45 | .arg(classpath) | 76 | .arg(classpath) |
| 46 | .arg(&version.main_class) | 77 | .arg(&version.main_class); |
| 47 | .arg("--username") | 78 | |
| 79 | // ===== ARGUMENTY GRY ===== | ||
| 80 | cmd.arg("--username") | ||
| 48 | .arg(&config.username) | 81 | .arg(&config.username) |
| 49 | .arg("--version") | 82 | .arg("--version") |
| 50 | .arg(&version.id) | 83 | .arg(&version.id) |
| 51 | .arg("--gameDir") | 84 | .arg("--gameDir") |
| 52 | .arg(paths::minecraft_root(config)) | 85 | .arg(paths::game_dir(config)) |
| 53 | .arg("--assetsDir") | 86 | .arg("--assetsDir") |
| 54 | .arg(paths::minecraft_root(config).join("assets")) | 87 | .arg(paths::assets_dir(config)) |
| 55 | .arg("--assetIndex") | 88 | .arg("--assetIndex") |
| 56 | .arg(&version.id) | 89 | .arg(&asset_index_id) |
| 57 | .arg("--uuid") | 90 | .arg("--uuid") |
| 58 | .arg(&config.uuid) | 91 | .arg(&config.uuid) |
| 59 | .arg("--userProperties") | 92 | .arg("--userProperties") |
| @@ -61,9 +94,9 @@ pub fn launch(config: &Config, version: &Version) -> Result<(), McError> { | |||
| 61 | .arg("--accessToken") | 94 | .arg("--accessToken") |
| 62 | .arg("0") | 95 | .arg("0") |
| 63 | .arg("--userType") | 96 | .arg("--userType") |
| 64 | .arg("legacy") | 97 | .arg("legacy"); // legacy dla starych kont, można później zmienić |
| 65 | .args(&config.jvm_args) | 98 | |
| 66 | .status()?; | 99 | let status = cmd.status()?; |
| 67 | 100 | ||
| 68 | if !status.success() { | 101 | if !status.success() { |
| 69 | return Err(McError::Process("Minecraft exited with error".into())); | 102 | return Err(McError::Process("Minecraft exited with error".into())); |
| @@ -71,3 +104,25 @@ pub fn launch(config: &Config, version: &Version) -> Result<(), McError> { | |||
| 71 | 104 | ||
| 72 | Ok(()) | 105 | Ok(()) |
| 73 | } | 106 | } |
| 107 | |||
| 108 | /// Sprawdza reguły bibliotek tak jak robi Mojang | ||
| 109 | fn library_allowed(lib: &Library) -> bool { | ||
| 110 | let rules = match &lib.rules { | ||
| 111 | | Some(r) => r, | ||
| 112 | | None => return true, | ||
| 113 | }; | ||
| 114 | |||
| 115 | let mut allowed = false; | ||
| 116 | |||
| 117 | for rule in rules { | ||
| 118 | let os_match = match &rule.os { | ||
| 119 | | Some(os) => os.name == "linux", | ||
| 120 | | None => true, | ||
| 121 | }; | ||
| 122 | if os_match { | ||
| 123 | allowed = rule.action == "allow"; | ||
| 124 | } | ||
| 125 | } | ||
| 126 | |||
| 127 | allowed | ||
| 128 | } | ||
diff --git a/src/minecraft/manifests.rs b/src/minecraft/manifests.rs index 3cc59af..8bdec26 100644 --- a/src/minecraft/manifests.rs +++ b/src/minecraft/manifests.rs | |||
| @@ -1,4 +1,7 @@ | |||
| 1 | #![allow(dead_code)] | 1 | #![allow(dead_code)] |
| 2 | |||
| 3 | use std::collections::HashMap; | ||
| 4 | |||
| 2 | use reqwest; | 5 | use reqwest; |
| 3 | use serde::Deserialize; | 6 | use serde::Deserialize; |
| 4 | 7 | ||
| @@ -13,6 +16,17 @@ pub struct Version { | |||
| 13 | 16 | ||
| 14 | pub downloads: Downloads, | 17 | pub downloads: Downloads, |
| 15 | pub libraries: Vec<Library>, | 18 | pub libraries: Vec<Library>, |
| 19 | |||
| 20 | #[serde(rename = "assetIndex")] | ||
| 21 | pub asset_index: Option<AssetIndex>, | ||
| 22 | } | ||
| 23 | |||
| 24 | #[derive(Debug, Deserialize)] | ||
| 25 | pub struct AssetIndex { | ||
| 26 | pub id: String, | ||
| 27 | pub sha1: String, | ||
| 28 | pub size: u64, | ||
| 29 | pub url: String, | ||
| 16 | } | 30 | } |
| 17 | 31 | ||
| 18 | #[derive(Debug, Deserialize)] | 32 | #[derive(Debug, Deserialize)] |
| @@ -29,12 +43,22 @@ pub struct DownloadInfo { | |||
| 29 | 43 | ||
| 30 | #[derive(Debug, Deserialize)] | 44 | #[derive(Debug, Deserialize)] |
| 31 | pub struct Library { | 45 | pub struct Library { |
| 46 | pub name: Option<String>, | ||
| 32 | pub downloads: LibraryDownloads, | 47 | pub downloads: LibraryDownloads, |
| 48 | |||
| 49 | #[serde(default)] | ||
| 50 | pub natives: Option<HashMap<String, String>>, | ||
| 51 | |||
| 52 | #[serde(default)] | ||
| 53 | pub rules: Option<Vec<Rule>>, | ||
| 33 | } | 54 | } |
| 34 | 55 | ||
| 35 | #[derive(Debug, Deserialize)] | 56 | #[derive(Debug, Deserialize)] |
| 36 | pub struct LibraryDownloads { | 57 | pub struct LibraryDownloads { |
| 37 | pub artifact: Option<LibraryArtifact>, | 58 | pub artifact: Option<LibraryArtifact>, |
| 59 | |||
| 60 | #[serde(default)] | ||
| 61 | pub classifiers: Option<HashMap<String, LibraryArtifact>>, | ||
| 38 | } | 62 | } |
| 39 | 63 | ||
| 40 | #[derive(Debug, Deserialize)] | 64 | #[derive(Debug, Deserialize)] |
| @@ -45,7 +69,20 @@ pub struct LibraryArtifact { | |||
| 45 | pub size: u64, | 69 | pub size: u64, |
| 46 | } | 70 | } |
| 47 | 71 | ||
| 48 | pub async fn load_version(cfg: &crate::config::Config) -> Result<Version, McError> { | 72 | #[derive(Debug, Deserialize)] |
| 73 | pub struct Rule { | ||
| 74 | pub action: String, | ||
| 75 | pub os: Option<OsRule>, | ||
| 76 | } | ||
| 77 | |||
| 78 | #[derive(Debug, Deserialize)] | ||
| 79 | pub struct OsRule { | ||
| 80 | pub name: String, | ||
| 81 | } | ||
| 82 | |||
| 83 | pub async fn load_version( | ||
| 84 | cfg: &crate::config::Config, | ||
| 85 | ) -> Result<Version, McError> { | ||
| 49 | let manifest_text = reqwest::get(VERSION_MANIFEST_URL) | 86 | let manifest_text = reqwest::get(VERSION_MANIFEST_URL) |
| 50 | .await? | 87 | .await? |
| 51 | .text() | 88 | .text() |
| @@ -67,7 +104,9 @@ pub async fn load_version(cfg: &crate::config::Config) -> Result<Version, McErro | |||
| 67 | let version_entry = versions | 104 | let version_entry = versions |
| 68 | .iter() | 105 | .iter() |
| 69 | .find(|v| v["id"].as_str() == Some(&version_id)) | 106 | .find(|v| v["id"].as_str() == Some(&version_id)) |
| 70 | .ok_or_else(|| McError::Config(format!("version '{}' not found", version_id)))?; | 107 | .ok_or_else(|| { |
| 108 | McError::Config(format!("version '{}' not found", version_id)) | ||
| 109 | })?; | ||
| 71 | 110 | ||
| 72 | let url = version_entry["url"] | 111 | let url = version_entry["url"] |
| 73 | .as_str() | 112 | .as_str() |
diff --git a/src/minecraft/mod.rs b/src/minecraft/mod.rs index f1ce1f0..6dea71d 100644 --- a/src/minecraft/mod.rs +++ b/src/minecraft/mod.rs | |||
| @@ -1,3 +1,18 @@ | |||
| 1 | //! Minecraft module for the DML launcher. | ||
| 2 | //! | ||
| 3 | //! This module provides the core functionality for interacting with | ||
| 4 | //! Minecraft game files, including downloading assets, extracting | ||
| 5 | //! game JARs, managing libraries, and launching the game. | ||
| 6 | //! | ||
| 7 | //! # Submodules | ||
| 8 | //! - `downloads`: Async functions for downloading game files and assets. | ||
| 9 | //! - `extraction`: Functions for extracting ZIP/JAR files to the correct | ||
| 10 | //! locations. | ||
| 11 | //! - `launcher`: Functions to launch the Minecraft process with the proper | ||
| 12 | //! arguments. | ||
| 13 | //! - `manifests`: Structures representing Minecraft version manifests, | ||
| 14 | //! libraries, and rules. | ||
| 15 | |||
| 1 | pub mod downloads; | 16 | pub mod downloads; |
| 2 | pub mod extraction; | 17 | pub mod extraction; |
| 3 | pub mod launcher; | 18 | pub mod launcher; |
