From eef238c1bb10d62dea35b8a29a5f66f9fca9451d Mon Sep 17 00:00:00 2001 From: didirus Date: Wed, 28 Jan 2026 01:06:48 +0300 Subject: [PATCH] refactor: remove init_authlib_patching function and update related references --- apps/app-frontend/src/helpers/utils.js | 5 - apps/app/build.rs | 1 - apps/app/src/api/auth.rs | 6 +- apps/app/src/api/utils.rs | 12 - apps/app/tauri.conf.json | 2 +- packages/app-lib/src/launcher/mod.rs | 27 +- packages/app-lib/src/state/discord.rs | 2 +- packages/app-lib/src/util/utils.rs | 557 +++++++++---------------- 8 files changed, 210 insertions(+), 402 deletions(-) diff --git a/apps/app-frontend/src/helpers/utils.js b/apps/app-frontend/src/helpers/utils.js index 139a05cf..2ebb9861 100644 --- a/apps/app-frontend/src/helpers/utils.js +++ b/apps/app-frontend/src/helpers/utils.js @@ -38,11 +38,6 @@ export async function applyMigrationFix(eol) { return await invoke('plugin:utils|apply_migration_fix', { eol }) } -// This code is modified by AstralRinth -export async function initAuthlibPatching(minecraftVersion, isMojang) { - return await invoke('plugin:utils|init_authlib_patching', { minecraftVersion, isMojang }) -} - export async function isNetworkMetered() { return await invoke('plugin:utils|is_network_metered') } diff --git a/apps/app/build.rs b/apps/app/build.rs index 7349117e..36f39e0e 100644 --- a/apps/app/build.rs +++ b/apps/app/build.rs @@ -228,7 +228,6 @@ fn main() { "utils", InlinedPlugin::new() .commands(&[ - "init_authlib_patching", "apply_migration_fix", "init_update_launcher", "get_os", diff --git a/apps/app/src/api/auth.rs b/apps/app/src/api/auth.rs index 58e857d5..dd8e56cf 100644 --- a/apps/app/src/api/auth.rs +++ b/apps/app/src/api/auth.rs @@ -21,7 +21,7 @@ pub fn init() -> TauriPlugin { .build() } -/// ### AR • Feature +/// This code is modified by AstralRinth /// Create new offline user #[tauri::command] pub async fn offline_login(name: &str) -> Result { @@ -29,7 +29,7 @@ pub async fn offline_login(name: &str) -> Result { Ok(credentials) } -/// ### AR • Feature +/// This code is modified by AstralRinth /// Create new Ely.by user #[tauri::command] pub async fn elyby_login( @@ -41,7 +41,7 @@ pub async fn elyby_login( Ok(credentials) } -/// ### AR • Feature +/// This code is modified by AstralRinth /// Authenticate Ely.by user #[tauri::command] pub async fn elyby_auth_authenticate( diff --git a/apps/app/src/api/utils.rs b/apps/app/src/api/utils.rs index 269fddfd..13f9d384 100644 --- a/apps/app/src/api/utils.rs +++ b/apps/app/src/api/utils.rs @@ -16,7 +16,6 @@ use url::Url; pub fn init() -> tauri::plugin::TauriPlugin { tauri::plugin::Builder::new("utils") .invoke_handler(tauri::generate_handler![ - init_authlib_patching, apply_migration_fix, init_update_launcher, get_os, @@ -31,17 +30,6 @@ pub fn init() -> tauri::plugin::TauriPlugin { .build() } -// This code is modified by AstralRinth -#[tauri::command] -pub async fn init_authlib_patching( - minecraft_version: &str, - is_mojang: bool, -) -> Result { - let result = - utils::init_authlib_patching(minecraft_version, is_mojang).await?; - Ok(result) -} - // This code is modified by AstralRinth #[tauri::command] pub async fn apply_migration_fix(eol: &str) -> Result { diff --git a/apps/app/tauri.conf.json b/apps/app/tauri.conf.json index 6c02b725..cffb9ee9 100644 --- a/apps/app/tauri.conf.json +++ b/apps/app/tauri.conf.json @@ -48,7 +48,7 @@ ] }, "productName": "AstralRinth App", - "version": "0.10.2401", + "version": "0.10.2701", "mainBinaryName": "AstralRinth App", "identifier": "AstralRinthApp", "plugins": { diff --git a/packages/app-lib/src/launcher/mod.rs b/packages/app-lib/src/launcher/mod.rs index a76d49a4..c2fa3ff6 100644 --- a/packages/app-lib/src/launcher/mod.rs +++ b/packages/app-lib/src/launcher/mod.rs @@ -1,6 +1,6 @@ //! Logic for launching Minecraft use crate::data::ModLoader; -use crate::event::emit::{emit_loading, emit_info, init_or_edit_loading}; +use crate::event::emit::{emit_info, emit_loading, init_or_edit_loading}; use crate::event::{LoadingBarId, LoadingBarType}; use crate::launcher::download::download_log_config; use crate::launcher::io::IOError; @@ -11,8 +11,8 @@ use crate::profile::QuickPlayType; use crate::state::{ AccountType, Credentials, JavaVersion, ProcessMetadata, ProfileInstallStage, }; -use crate::util::{io, utils}; use crate::util::rpc::RpcServerBuilder; +use crate::util::{io, utils}; use crate::{State, get_resource_file, process, state as st}; use chrono::Utc; use daedalus as d; @@ -671,30 +671,33 @@ pub async fn launch_minecraft( if version_jar == "1.16.4" || version_jar == "1.16.5" { let invalid_url = "https://invalid.invalid"; let _ = emit_info(&format!( - "[AR] • Detected launch of {} on the offline account. Applying 1.16.4/5 multiplayer fixes.", + "[AR] Detected launch of {} on the offline account. Applying 1.16.4/5 multiplayer fixes.", version_jar ) ).await; command.arg("-Dminecraft.api.env=custom"); command.arg(format!("-Dminecraft.api.auth.host={}", invalid_url)); - command.arg(format!("-Dminecraft.api.account.host={}", invalid_url)); - command.arg(format!("-Dminecraft.api.session.host={}", invalid_url)); - command.arg(format!("-Dminecraft.api.services.host={}", invalid_url)); + command + .arg(format!("-Dminecraft.api.account.host={}", invalid_url)); + command + .arg(format!("-Dminecraft.api.session.host={}", invalid_url)); + command + .arg(format!("-Dminecraft.api.services.host={}", invalid_url)); } } else if credentials.account_type == AccountType::ElyBy.as_lowercase_str() { let _ = emit_info(&format!( - "[AR] • Detected launch of {} on the Ely.by account. Loading Ely.by AuthLib Injector...", + "[AR] Detected launch of {} on the Ely.by account. Loading Ely.by AuthLib Injector...", version_jar ) ).await; - let path_buf = utils::get_or_download_elyby_injector().await?; + let path_buf = utils::get_elyby_injector_library().await?; let path = path_buf.to_str().unwrap(); - let _ = emit_info(&format!( - "[AR] • Launching minecraft instance with {}", + let _ = emit_info(&format!( + "[AR] Launching minecraft instance with {}", path - ) - ).await; + )) + .await; command.arg(format!("-javaagent:{}=ely.by", path)); } diff --git a/packages/app-lib/src/state/discord.rs b/packages/app-lib/src/state/discord.rs index 792de7db..37de5458 100644 --- a/packages/app-lib/src/state/discord.rs +++ b/packages/app-lib/src/state/discord.rs @@ -116,7 +116,7 @@ impl DiscordGuard { .large_image("astralrinth_logo") .large_text(&build_info) .small_image("astralrinth_logo") - .small_text(&build_download), + .small_text(build_download), ) .timestamps(Timestamps::new().start(time)); diff --git a/packages/app-lib/src/util/utils.rs b/packages/app-lib/src/util/utils.rs index 45735956..d1d40de4 100644 --- a/packages/app-lib/src/util/utils.rs +++ b/packages/app-lib/src/util/utils.rs @@ -1,11 +1,14 @@ -use crate::api::update; -use crate::state::db; /// /// This code is modified by AstralRinth /// -/// Version: 0.1.1 +/// Version: 0.1.2 /// +/// +use crate::api::update; +use crate::event::emit::emit_info; +use crate::state::db; use crate::{Result, State}; + use serde::{Deserialize, Serialize}; use std::path::PathBuf; use std::process; @@ -16,301 +19,123 @@ const PACKAGE_JSON_CONTENT: &str = // include_str!("../../../../apps/app-frontend/package.json"); include_str!("../../../../apps/app/tauri.conf.json"); -#[derive(Serialize, Deserialize)] -pub struct Launcher { - pub version: String, -} - -#[derive(Debug, Deserialize)] -struct Artifact { - path: Option, - sha1: Option, - url: Option, -} - -#[derive(Debug, Deserialize)] -struct Downloads { - artifact: Option, -} - -#[derive(Debug, Deserialize)] -struct Library { - name: String, - downloads: Option, -} - -#[derive(Debug, Deserialize)] -struct VersionJson { - libraries: Vec, -} - /// Deserialize the content of package.json into a Launcher struct pub fn read_package_json() -> io::Result { let launcher: Launcher = serde_json::from_str(PACKAGE_JSON_CONTENT)?; Ok(launcher) } -/// ### AR • Ely.by Injector -/// Returns the PathBuf to the Ely.by AuthLib Injector -/// If resource doesn't exist or outdated, it will be downloaded from Git Astralium. -pub async fn get_or_download_elyby_injector() -> Result { - tracing::info!("[AR] • Initializing state for Ely.by AuthLib Injector..."); +#[derive(Serialize, Deserialize)] +pub struct Launcher { + pub version: String, +} + +/// Fetches or updates the Ely.by AuthLib Injector library. +pub async fn get_elyby_injector_library() -> Result { + tracing::info!("[AR] • Initializing Ely.by AuthLib Injector..."); let state = State::get().await?; let libraries_dir = state.directories.libraries_dir(); - // Stores the local authlib injectors from `libraries/astralrinth/authlib_injectors/` directory. - let mut local_authlib_injectors = Vec::new(); + validate_library_dir(&libraries_dir, "authlib_injector/").await?; + let injector_dir = libraries_dir.join("astralrinth/authlib_injector/"); + fs::create_dir_all(&injector_dir).await?; - validate_astralrinth_library_dir(&libraries_dir, "authlib_injector/").await?; - let astralrinth_dir = libraries_dir.join("astralrinth/"); - let authlib_injector_dir = astralrinth_dir.join("authlib_injector/"); - let mut authlib_injector_dir_data = fs::read_dir(&authlib_injector_dir).await?; - - // Get all local authlib injectors - while let Some(entry) = authlib_injector_dir_data.next_entry().await? { - let path = entry.path(); - if let Some(file_name) = path.file_name().and_then(|s| s.to_str()) { - if file_name.starts_with("authlib-injector") { - let metadata = entry.metadata().await?; - let modified = metadata.modified().unwrap_or(SystemTime::UNIX_EPOCH); - local_authlib_injectors.push((path.clone(), modified)); + let mut local_injectors = Vec::new(); + if let Ok(mut entries) = fs::read_dir(&injector_dir).await { + while let Ok(Some(entry)) = entries.next_entry().await { + let path = entry.path(); + if let (Some(name), Ok(meta)) = ( + path.file_name().and_then(|s| s.to_str()), + entry.metadata().await, + ) { + if name.starts_with("authlib-injector") { + local_injectors.push(( + path, + meta.modified().unwrap_or(SystemTime::UNIX_EPOCH), + )); + } } } } + local_injectors.sort_by(|a, b| b.1.cmp(&a.1)); // newest first - // Get information about latest authlib injector from remote repository - let (asset_name, download_url) = match extract_elyby_authlib_metadata("authlib-injector").await { - Ok(data) => data, - Err(err) => { - if let Some((local_path, _)) = local_authlib_injectors - .iter() - .max_by(|a, b| a.1.cmp(&b.1)) - { - tracing::info!("[AR] • Found local AuthLib Injector(s):"); - for (path, time) in &local_authlib_injectors { - tracing::info!("• {:?} (modified: {:?})", path.file_name().unwrap(), time); - } - tracing::warn!("[AR] • Failed to get latest AuthLib Injector from remote, using latest local version: {}", local_path.display()); - return Ok(local_path.clone()); - } else { - tracing::error!("[AR] • Failed to get AuthLib Injector from remote and no local copy found."); - return Err(crate::ErrorKind::NetworkErrorOccurred { error: format!("Failed to fetch authlib-injector metadata and no local version available: {}", err) }.as_error()); + if !local_injectors.is_empty() { + tracing::info!("[AR] • Local versions:"); + for (path, mtime) in &local_injectors { + tracing::info!(" • {:?} ({:?})", path.file_name().unwrap(), mtime); + } + } + + let latest_local = local_injectors.first().cloned(); + // let latest_local_name = latest_local.as_ref().map(|p| p.0.file_name().unwrap().to_string_lossy().to_string()); + + // Remote (fallback to empty strings) + let (remote_name, remote_url) = + match extract_metadata_from_elyby_file("authlib-injector").await { + Ok(data) => { + tracing::info!("[AR] • Remote: {} ({})", data.0, data.1); + data } - } + Err(e) => { + tracing::warn!("[AR] • Remote failed: {}, using local", e); + ("".to_string(), "".to_string()) + } + }; + + let remote_path = if !remote_name.is_empty() { + Some(injector_dir.join(&remote_name)) + } else { + None }; - if !local_authlib_injectors.is_empty() { - local_authlib_injectors.sort_by(|a, b| a.1.cmp(&b.1)); - tracing::info!("[AR] • Found local AuthLib Injector(s):"); - for (path, time) in &local_authlib_injectors { - tracing::info!("• {:?} (modified: {:?})", path.file_name().unwrap(), time); + if let Some(local_path) = &latest_local { + let local_name = local_path.0.file_name().unwrap().to_string_lossy(); + if let Some(rp) = &remote_path { + let remote_name = rp.file_name().unwrap().to_string_lossy(); + if local_name == remote_name { + tracing::info!("[AR] • Versions match: {}", local_name); + return Ok(local_path.0.clone()); + } + } else { + tracing::info!( + "[AR] • No remote info, using local: {}", + local_name + ); + let _ = emit_info(&format!( + "[AR] No remote info, using local: {}", + local_name + )) + .await; + return Ok(local_path.0.clone()); } } - let remote_authlib_injector = if !asset_name.is_empty() { - authlib_injector_dir.join(&asset_name) - } else { - return Err(crate::ErrorKind::ParseError { reason: "Asset name is empty from metadata".to_string() }.as_error()); + let Some(rp) = remote_path else { + return Err(crate::ErrorKind::NetworkErrorOccurred { + error: "No local injector & remote unavailable".to_string(), + } + .as_error()); }; - let latest_local_authlib_injector = local_authlib_injectors - .first() - .map(|(p, _)| p.clone()); + let fname = rp.file_name().unwrap().to_string_lossy(); + tracing::info!("[AR] • Downloading: {}", fname); + let _ = emit_info(&format!("[AR] Downloading: {}", fname)).await; - let latest_local_authlib_injector_full_path_buf = match latest_local_authlib_injector { - Some(path) => path, - None => { - tracing::info!("[AR] • No local version found, will download from remote: {}", remote_authlib_injector.display()); - let bytes = fetch_bytes_from_url(download_url.as_str()).await?; - write_file_to_libraries(&remote_authlib_injector.to_string_lossy(), &bytes).await?; - tracing::info!("[AR] • Successfully saved AuthLib Injector to {}", remote_authlib_injector.display()); - return Ok(remote_authlib_injector); - } - }; + let bytes = fetch_bytes_from_url(&remote_url).await?; + let rel_path = rp + .strip_prefix(&libraries_dir)? + .to_string_lossy() + .into_owned(); + write_file_to_libraries(&rel_path, &bytes).await?; - tracing::info!("[AR] • Remote Asset name: {}", asset_name); - tracing::info!("[AR] • Remote Download URL: {}", download_url); - tracing::info!("[AR] • Latest local AuthLib Injector: {}", latest_local_authlib_injector_full_path_buf.file_name().unwrap().to_string_lossy()); - tracing::info!("[AR] • Comparing local version {} with parsed remote version {}", latest_local_authlib_injector_full_path_buf.display(), remote_authlib_injector.display()); - - if remote_authlib_injector == latest_local_authlib_injector_full_path_buf { - tracing::info!("[AR] • Remote version is the same as local version, using local copy."); - return Ok(latest_local_authlib_injector_full_path_buf); - } else { - tracing::info!( - "[AR] • Doesn't exist or outdated, attempting to download latest AuthLib Injector from URL: {}", - download_url - ); - let bytes = fetch_bytes_from_url(download_url.as_str()).await?; - write_file_to_libraries(&remote_authlib_injector.to_string_lossy(), &bytes).await?; - tracing::info!("[AR] • Successfully saved AuthLib Injector to {}", remote_authlib_injector.display()); - return Ok(remote_authlib_injector); - } + tracing::info!("[AR] • Saved: {}", rp.display()); + let _ = emit_info(&format!("[AR] Saved: {}", rp.display())).await; + Ok(rp.to_path_buf()) } -/// ### AR • Migration. Patch -/// Applying migration fix for SQLite database. -pub async fn apply_migration_fix(eol: &str) -> Result { - tracing::info!("[AR] • Attempting to apply migration fix"); - let patched = db::apply_migration_fix(eol).await?; - if patched { - tracing::info!("[AR] • Successfully applied migration fix"); - } else { - tracing::error!("[AR] • Failed to apply migration fix"); - } - Ok(patched) -} - -/// ### AR • Feature. Updater -/// Initialize the update launcher. -pub async fn init_update_launcher( - download_url: &str, - local_filename: &str, - os_type: &str, - auto_update_supported: bool, -) -> Result<()> { - tracing::info!("[AR] • Initialize downloading from • {:?}", download_url); - tracing::info!("[AR] • Save local file name • {:?}", local_filename); - tracing::info!("[AR] • OS type • {}", os_type); - tracing::info!("[AR] • Auto update supported • {}", auto_update_supported); - - if let Err(e) = update::get_resource( - download_url, - local_filename, - os_type, - auto_update_supported, - ) - .await - { - eprintln!( - "[AR] • An error occurred! Failed to download the file: {}", - e - ); - } else { - println!("[AR] • Code finishes without errors."); - process::exit(0) - } - Ok(()) -} - -/// ### AR • AuthLib (Ely.by) -/// Initializes the AuthLib patching process. -/// -/// Returns `true` if the authlib patched successfully. -pub async fn init_authlib_patching( - minecraft_version: &str, - is_mojang: bool, -) -> Result { - let minecraft_library_metadata = - get_minecraft_library_metadata(minecraft_version).await?; - // Parses the AuthLib version from string - // Example output: "com.mojang:authlib:6.0.58" -> "6.0.58" - let authlib_version = minecraft_library_metadata - .name - .split(':') - .nth(2) - .unwrap_or("unknown"); - let authlib_fullname_string = format!("authlib-{}.jar", authlib_version); - let authlib_fullname_str = authlib_fullname_string.as_str(); - - tracing::info!( - "[AR] • Attempting to download AuthLib -> {}.", - authlib_fullname_string - ); - - download_authlib( - &minecraft_library_metadata, - authlib_fullname_str, - minecraft_version, - is_mojang, - ) - .await -} - -/// ### AR • Universal Write (IO) Function. -/// Validating the `astralrinth/{target_directory}/` directory exists inside the libraries/astralrinth directory. -async fn validate_astralrinth_library_dir( - libraries_dir: &PathBuf, - validation_directory: &str -) -> Result<()> { - let astralrinth_path = libraries_dir.join(format!("astralrinth/{}", validation_directory)); - if !astralrinth_path.exists() { - tokio::fs::create_dir_all(&astralrinth_path) - .await - .map_err(|e| { - tracing::error!( - "[AR] • Failed to create {} directory: {:?}", - astralrinth_path.display(), - e - ); - crate::ErrorKind::IOErrorOccurred { - error: format!( - "Failed to create {} directory: {}", - astralrinth_path.display(), - e - ), - } - .as_error() - })?; - tracing::info!( - "[AR] • Created missing {} directory", - astralrinth_path.display() - ); - } - Ok(()) -} - -/// ### AR • Universal Write (IO) Function -/// Saves the downloaded bytes to the `libraries` directory using the given relative path. -async fn write_file_to_libraries( - relative_path: &str, - bytes: &bytes::Bytes, -) -> Result<()> { - let state = State::get().await?; - let output_path = state.directories.libraries_dir().join(relative_path); - - fs::write(&output_path, bytes).await.map_err(|e| { - tracing::error!("[AR] • Failed to save file: {:?}", e); - crate::ErrorKind::IOErrorOccurred { - error: format!("Failed to save file: {e}"), - } - .as_error() - }) -} - -/// ### AR • AuthLib (Ely.by) -/// Downloads the AuthLib file from Mojang libraries or Git Astralium services. -async fn download_authlib( - minecraft_library_metadata: &Library, - authlib_fullname: &str, - minecraft_version: &str, - is_mojang: bool, -) -> Result { - let state = State::get().await?; - let (mut url, path) = extract_minecraft_local_download_info( - minecraft_library_metadata, - minecraft_version, - )?; - let full_path = state.directories.libraries_dir().join(path); - - if !is_mojang { - tracing::info!( - "[AR] • Attempting to download AuthLib from Git Astralium" - ); - (_, url) = extract_elyby_authlib_metadata(authlib_fullname).await?; - } - tracing::info!("[AR] • Downloading AuthLib from URL: {}", url); - let bytes = fetch_bytes_from_url(&url).await?; - tracing::info!("[AR] • Will save to path: {}", full_path.to_str().unwrap()); - write_file_to_libraries(full_path.to_str().unwrap(), &bytes).await?; - tracing::info!("[AR] • Successfully saved AuthLib to {:?}", full_path); - Ok(true) -} - -/// ### AR • AuthLib (Ely.by) /// Parses the ElyIntegration release JSON and returns the download URL for the given AuthLib version. -async fn extract_elyby_authlib_metadata( - authlib_fullname: &str, +async fn extract_metadata_from_elyby_file( + file_name: &str, ) -> Result<(String, String)> { const URL: &str = "https://git.astralium.su/api/v1/repos/didirus/ElyIntegration/releases/latest"; @@ -351,14 +176,14 @@ async fn extract_elyby_authlib_metadata( .find(|a| { a.get("name") .and_then(|n| n.as_str()) - .map(|n| n.contains(authlib_fullname)) + .map(|n| n.contains(file_name)) .unwrap_or(false) }) .ok_or_else(|| { crate::ErrorKind::ParseError { reason: format!( "No matching asset for {} in ElyIntegration JSON response.", - authlib_fullname + file_name ), } .as_error() @@ -389,49 +214,104 @@ async fn extract_elyby_authlib_metadata( Ok((asset_name, download_url)) } -/// ### AR • AuthLib (Ely.by) -/// Extracts the artifact URL and Path from the library structure. -/// -/// Returns a tuple of references to the URL and path strings, -/// or an error if the required metadata is missing. -fn extract_minecraft_local_download_info( - minecraft_library_metadata: &Library, - minecraft_version: &str, -) -> Result<(String, String)> { - let artifact = minecraft_library_metadata - .downloads - .as_ref() - .and_then(|d| d.artifact.as_ref()) - .ok_or_else(|| { - crate::ErrorKind::MinecraftMetadataNotFound { - minecraft_version: minecraft_version.to_string(), - } - .as_error() - })?; - - let url = artifact.url.clone().ok_or_else(|| { - crate::ErrorKind::MinecraftMetadataNotFound { - minecraft_version: minecraft_version.to_string(), - } - .as_error() - })?; - - let path = artifact.path.clone().ok_or_else(|| { - crate::ErrorKind::MinecraftMetadataNotFound { - minecraft_version: minecraft_version.to_string(), - } - .as_error() - })?; - - Ok((url, path)) +/// Applying migration fix for SQLite database. +pub async fn apply_migration_fix(eol: &str) -> Result { + tracing::info!("[AR] • Attempting to apply migration fix"); + let patched = db::apply_migration_fix(eol).await?; + if patched { + tracing::info!("[AR] • Successfully applied migration fix"); + } else { + tracing::error!("[AR] • Failed to apply migration fix"); + } + Ok(patched) +} + +/// Initialize the update launcher. +pub async fn init_update_launcher( + download_url: &str, + local_filename: &str, + os_type: &str, + auto_update_supported: bool, +) -> Result<()> { + tracing::info!("[AR] • Initialize downloading from • {:?}", download_url); + tracing::info!("[AR] • Save local file name • {:?}", local_filename); + tracing::info!("[AR] • OS type • {}", os_type); + tracing::info!("[AR] • Auto update supported • {}", auto_update_supported); + + if let Err(e) = update::get_resource( + download_url, + local_filename, + os_type, + auto_update_supported, + ) + .await + { + eprintln!( + "[AR] • An error occurred! Failed to download the file: {}", + e + ); + } else { + println!("[AR] • Code finishes without errors."); + process::exit(0) + } + Ok(()) +} + +/// Validating the `astralrinth/{target_directory}/` directory exists inside the libraries/astralrinth directory. +async fn validate_library_dir( + libraries_dir: &PathBuf, + validation_directory: &str, +) -> Result<()> { + let astralrinth_path = + libraries_dir.join(format!("astralrinth/{}", validation_directory)); + if !astralrinth_path.exists() { + tokio::fs::create_dir_all(&astralrinth_path) + .await + .map_err(|e| { + tracing::error!( + "[AR] • Failed to create {} directory: {:?}", + astralrinth_path.display(), + e + ); + crate::ErrorKind::IOErrorOccurred { + error: format!( + "Failed to create {} directory: {}", + astralrinth_path.display(), + e + ), + } + .as_error() + })?; + tracing::info!( + "[AR] • Created missing {} directory", + astralrinth_path.display() + ); + } + Ok(()) +} + +/// Saves the downloaded bytes to the `libraries` directory using the given relative path. +async fn write_file_to_libraries( + relative_path: &str, + bytes: &bytes::Bytes, +) -> Result<()> { + let state = State::get().await?; + let output_path = state.directories.libraries_dir().join(relative_path); + + fs::write(&output_path, bytes).await.map_err(|e| { + tracing::error!("[AR] • Failed to save file: {:?}", e); + crate::ErrorKind::IOErrorOccurred { + error: format!("Failed to save file: {e}"), + } + .as_error() + }) } -/// ### AR • Universal Fetch Bytes (IO) /// Downloads bytes from the provided URL with a 15 second timeout. async fn fetch_bytes_from_url(url: &str) -> Result { // Create client instance with request timeout. let client = reqwest::Client::new(); - const TIMEOUT_SECONDS: u64 = 15; + const TIMEOUT_SECONDS: u64 = 5; let response = tokio::time::timeout( std::time::Duration::from_secs(TIMEOUT_SECONDS), @@ -476,60 +356,3 @@ async fn fetch_bytes_from_url(url: &str) -> Result { .as_error() }) } - -/// ### AR • AuthLib (Ely.by) -/// Gets the Minecraft library metadata from the local libraries directory. -async fn get_minecraft_library_metadata( - minecraft_version: &str, -) -> Result { - let state = State::get().await?; - - let path = state - .directories - .version_dir(minecraft_version) - .join(format!("{}.json", minecraft_version)); - if !path.exists() { - tracing::error!("[AR] • File not found: {:#?}", path); - return Err(crate::ErrorKind::InvalidMinecraftVersion { - minecraft_version: minecraft_version.to_string(), - } - .as_error()); - } - - let content = fs::read_to_string(&path).await?; - let version_data: VersionJson = serde_json::from_str(&content)?; - - for lib in version_data.libraries { - if lib.name.contains("com.mojang:authlib") { - if let Some(downloads) = &lib.downloads { - if let Some(artifact) = &downloads.artifact { - if artifact.path.is_some() - && artifact.url.is_some() - && artifact.sha1.is_some() - { - tracing::info!("[AR] • Found AuthLib: {}", lib.name); - tracing::info!( - "[AR] • Path: {}", - artifact.path.as_ref().unwrap() - ); - tracing::info!( - "[AR] • URL: {}", - artifact.url.as_ref().unwrap() - ); - tracing::info!( - "[AR] • SHA1: {}", - artifact.sha1.as_ref().unwrap() - ); - - return Ok(lib); - } - } - } - } - } - - Err(crate::ErrorKind::MinecraftMetadataNotFound { - minecraft_version: minecraft_version.to_string(), - } - .as_error()) -}