feat: Downloading game assets + download minecraft example
This commit is contained in:
parent
1f2d560983
commit
81ed2de0f5
13 changed files with 345 additions and 192 deletions
19
packages/core/examples/download_minecraft.rs
Normal file
19
packages/core/examples/download_minecraft.rs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
use anyhow::anyhow;
|
||||
use onelauncher_core::{api::{game::metadata::{download_minecraft_ingressed, download_version_info}, proxy::ProxyDynamic}, error::LauncherResult, initialize_core, store::{CoreOptions, State}};
|
||||
use onelauncher_entity::loader::GameLoader;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> LauncherResult<()> {
|
||||
initialize_core(CoreOptions::default(), ProxyDynamic::new()).await?;
|
||||
|
||||
let state = State::get().await?;
|
||||
let mut metadata = state.metadata.write().await;
|
||||
let versions = metadata.get_vanilla(GameLoader::Vanilla).await?;
|
||||
|
||||
let version = versions.versions.iter().find(|v| v.id == "1.8.9").ok_or_else(|| anyhow!("couldn't find 1.8.9 in version list"))?;
|
||||
let version_info = download_version_info(version, None, None, Some(true)).await?;
|
||||
|
||||
download_minecraft_ingressed(&version_info, Some(true)).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
188
packages/core/src/api/game/metadata.rs
Normal file
188
packages/core/src/api/game/metadata.rs
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
use futures::{StreamExt, stream};
|
||||
use interpulse::api::minecraft::{AssetsIndex, Version, VersionInfo};
|
||||
use interpulse::api::modded::LoaderVersion;
|
||||
use reqwest::Method;
|
||||
|
||||
use crate::api::ingress::{init_ingress, send_ingress_ref_opt};
|
||||
use crate::error::LauncherResult;
|
||||
use crate::store::ingress::{IngressRef, IngressType};
|
||||
use crate::store::{Core, Dirs};
|
||||
use crate::utils::crypto::HashAlgorithm;
|
||||
use crate::utils::http::{self, fetch_json_advanced};
|
||||
use crate::utils::io;
|
||||
|
||||
pub async fn download_minecraft_ingressed(version: &VersionInfo, force: Option<bool>) -> LauncherResult<()> {
|
||||
let id = init_ingress(
|
||||
IngressType::MinecraftDownload,
|
||||
&format!("Downloading Minecraft {}", version.id),
|
||||
100.0,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let ingress_ref = IngressRef::new(&id, 100.0);
|
||||
|
||||
download_minecraft(version, Some(&ingress_ref), force).await
|
||||
}
|
||||
|
||||
// MARK: Main
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub async fn download_minecraft(version: &VersionInfo, ingress_ref: Option<&IngressRef<'_>>, force: Option<bool>) -> LauncherResult<()> {
|
||||
|
||||
const TASKS: f64 = 2.0;
|
||||
let ingress_ref = ingress_ref.map(|i| i.with_increment(i.increment_by / TASKS));
|
||||
let ingress_ref = ingress_ref.as_ref();
|
||||
|
||||
let asset_index = download_assets_index(version, ingress_ref, force).await?;
|
||||
tokio::try_join! {
|
||||
download_assets(version.assets == "legacy", &asset_index, ingress_ref, force)
|
||||
}?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// MARK: Version Info
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub async fn download_version_info(
|
||||
version: &Version,
|
||||
loader: Option<&LoaderVersion>,
|
||||
ingress_ref: Option<&IngressRef<'_>>,
|
||||
force: Option<bool>,
|
||||
) -> LauncherResult<VersionInfo> {
|
||||
let version_id = loader.map_or(version.id.clone(), |it| format!("{}-{}", version.id, it.id));
|
||||
|
||||
let ingress_ref = ingress_ref.map(|i| i.with_increment(i.increment_by / 3.33));
|
||||
let ingress_ref = ingress_ref.as_ref();
|
||||
|
||||
tracing::debug!("loading minecraft version info for minecraft version {version_id}");
|
||||
|
||||
let path = Dirs::get_versions_dir()
|
||||
.await?
|
||||
.join(&version_id)
|
||||
.join(format!("{version_id}.json"));
|
||||
|
||||
let result = if path.exists() && !force.unwrap_or(false) {
|
||||
let data = io::read(path).await?;
|
||||
serde_json::from_slice(&data)?
|
||||
} else {
|
||||
tracing::info!(
|
||||
"downloading minecraft version info for minecraft version {}",
|
||||
&version_id
|
||||
);
|
||||
|
||||
let mut info =
|
||||
fetch_json_advanced(Method::GET, &version.url, None, None, None, ingress_ref).await?;
|
||||
|
||||
if let Some(loader) = loader {
|
||||
let partial: interpulse::api::modded::PartialVersionInfo =
|
||||
fetch_json_advanced(Method::GET, &loader.url, None, None, None, ingress_ref)
|
||||
.await?;
|
||||
|
||||
info = interpulse::api::modded::merge_partial_version(partial, info);
|
||||
}
|
||||
|
||||
info.id.clone_from(&version_id);
|
||||
|
||||
io::create_dir_all(&path.parent().unwrap_or_else(|| panic!("couldn't get version path parent"))).await?;
|
||||
io::write(&path, &serde_json::to_vec(&info)?).await?;
|
||||
info
|
||||
};
|
||||
|
||||
send_ingress_ref_opt(ingress_ref).await?;
|
||||
|
||||
tracing::debug!("loaded minecraft version info for minecraft version {version_id}");
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
// MARK: Assets Index
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub async fn download_assets_index(
|
||||
version: &VersionInfo,
|
||||
ingress_ref: Option<&IngressRef<'_>>,
|
||||
force: Option<bool>,
|
||||
) -> LauncherResult<AssetsIndex> {
|
||||
let path = Dirs::get_assets_index_dir()
|
||||
.await?
|
||||
.join(version.asset_index.id.clone());
|
||||
|
||||
let ingress_ref = ingress_ref.map(|i| i.with_increment(i.increment_by / 2.0));
|
||||
let ingress_ref = ingress_ref.as_ref();
|
||||
send_ingress_ref_opt(ingress_ref).await?;
|
||||
|
||||
if path.exists() && !force.unwrap_or(false) {
|
||||
match io::read_json::<AssetsIndex>(&path).await {
|
||||
Ok(data) => {
|
||||
send_ingress_ref_opt(ingress_ref).await?;
|
||||
return Ok(data);
|
||||
}
|
||||
Err(err) => tracing::error!(
|
||||
"failed to read assets index from cache: {err:?}, downloading again",
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
let data = serde_json::from_slice(&http::download(
|
||||
Method::GET,
|
||||
&version.asset_index.url,
|
||||
path,
|
||||
None,
|
||||
ingress_ref,
|
||||
)
|
||||
.await?)?;
|
||||
|
||||
tracing::debug!("loaded assets index for version {}", version.id);
|
||||
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
// MARK: Assets
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub async fn download_assets(
|
||||
legacy: bool,
|
||||
assets_index: &AssetsIndex,
|
||||
ingress_ref: Option<&IngressRef<'_>>,
|
||||
force: Option<bool>,
|
||||
) -> LauncherResult<()> {
|
||||
let len = assets_index.objects.len();
|
||||
|
||||
let dir = if legacy {
|
||||
Dirs::get_legacy_assets_dir().await
|
||||
} else {
|
||||
Dirs::get_assets_object_dir().await
|
||||
}?;
|
||||
|
||||
io::create_dir_all(&dir).await?;
|
||||
|
||||
let ingress_ref = ingress_ref.map(|i| i.with_increment(i.increment_by / len as f64));
|
||||
let ingress_ref = ingress_ref.as_ref();
|
||||
|
||||
let requests = stream::iter(assets_index.objects.iter().map(|(name, asset)| async {
|
||||
let hash = &asset.hash;
|
||||
let subhash = &hash[0..2];
|
||||
let path = if legacy {
|
||||
dir.join(name.replace('/', std::path::MAIN_SEPARATOR_STR))
|
||||
} else {
|
||||
dir.join(subhash).join(hash)
|
||||
};
|
||||
|
||||
if !path.exists() || force.unwrap_or(false) {
|
||||
http::download_advanced(
|
||||
Method::GET,
|
||||
&format!("https://resources.download.minecraft.net/{subhash}/{hash}"),
|
||||
path,
|
||||
None,
|
||||
None,
|
||||
Some((HashAlgorithm::Sha1, hash)),
|
||||
ingress_ref,
|
||||
)
|
||||
.await.map(|_| ())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}))
|
||||
.buffer_unordered(Core::get().fetch_attempts.min(7))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
requests.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
pub mod cluster;
|
||||
pub mod ingress;
|
||||
pub mod game;
|
||||
pub mod proxy;
|
||||
pub mod java;
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
use interpulse::api::{minecraft::{Version, VersionInfo}, modded::LoaderVersion};
|
||||
|
||||
use crate::{api::ingress::{init_ingress, send_ingress_ref_opt}, error::LauncherResult, store::{ingress::{IngressRef, IngressType}, Dirs}, utils::{http::fetch_json_advanced, io}};
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn download_minecraft(version: &VersionInfo) -> LauncherResult<()> {
|
||||
let id = init_ingress(IngressType::MinecraftDownload, "Downloading Minecraft", 100.0).await?;
|
||||
|
||||
// tokio::try_join! {
|
||||
|
||||
// }?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn download_version_info(
|
||||
version: &Version,
|
||||
loader: Option<&LoaderVersion>,
|
||||
force: Option<bool>,
|
||||
ingress_ref: Option<&IngressRef<'_>>,
|
||||
) -> LauncherResult<VersionInfo> {
|
||||
let version_id = loader.map_or(version.id.clone(), |it| format!("{}-{}", version.id, it.id));
|
||||
|
||||
let ingress_ref = ingress_ref.map(|i| i.with_increment(i.increment_by / 3.33));
|
||||
let ingress_ref = ingress_ref.as_ref();
|
||||
|
||||
tracing::debug!("loading minecraft version info for minecraft version {version_id}");
|
||||
|
||||
let path = Dirs::get_version_infos_dir().await?.join(format!("{version_id}.json"));
|
||||
|
||||
let result = if path.exists() && !force.unwrap_or(false) {
|
||||
let data = io::read(path).await?;
|
||||
serde_json::from_slice(&data)?
|
||||
} else {
|
||||
tracing::info!(
|
||||
"downloading minecraft version info for minecraft version {}",
|
||||
&version_id
|
||||
);
|
||||
|
||||
let mut info = fetch_json_advanced(
|
||||
reqwest::Method::GET,
|
||||
&version.url,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
ingress_ref
|
||||
).await?;
|
||||
|
||||
if let Some(loader) = loader {
|
||||
let partial: interpulse::api::modded::PartialVersionInfo = fetch_json_advanced(
|
||||
reqwest::Method::GET,
|
||||
&loader.url,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
ingress_ref
|
||||
).await?;
|
||||
|
||||
info = interpulse::api::modded::merge_partial_version(partial, info);
|
||||
}
|
||||
|
||||
info.id.clone_from(&version_id);
|
||||
|
||||
io::write(&path, &serde_json::to_vec(&info)?).await?;
|
||||
info
|
||||
};
|
||||
|
||||
send_ingress_ref_opt(ingress_ref).await?;
|
||||
|
||||
tracing::debug!("loaded minecraft version info for minecraft version {version_id}");
|
||||
Ok(result)
|
||||
}
|
||||
|
|
@ -8,7 +8,6 @@ use logger::start_logger;
|
|||
pub mod api;
|
||||
pub mod constants;
|
||||
pub mod error;
|
||||
pub mod game;
|
||||
pub mod store;
|
||||
pub mod utils;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,23 +1,27 @@
|
|||
use tracing_subscriber::{prelude::*, util::SubscriberInitExt};
|
||||
use crate::store::Core;
|
||||
|
||||
// Handling for the live development logging
|
||||
// This will log to the console, and will not log to a file
|
||||
#[cfg(debug_assertions)]
|
||||
pub async fn start_logger() {
|
||||
use tracing_subscriber::{fmt::format::FmtSpan, prelude::*, util::SubscriberInitExt};
|
||||
|
||||
let filter = tracing_subscriber::EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new(format!("{}=info", env!("CARGO_PKG_NAME"))));
|
||||
|
||||
let mut fmt_layer = tracing_subscriber::fmt::layer()
|
||||
.with_ansi(true)
|
||||
.with_file(true)
|
||||
.with_line_number(true)
|
||||
.with_level(true)
|
||||
.with_thread_names(true)
|
||||
.pretty();
|
||||
|
||||
if let Some(span) = Core::get().span_formatting.clone() {
|
||||
fmt_layer = fmt_layer.with_span_events(span);
|
||||
}
|
||||
|
||||
if let Err(err) = tracing_subscriber::registry()
|
||||
.with(
|
||||
tracing_subscriber::fmt::layer()
|
||||
.with_ansi(true)
|
||||
.with_file(true)
|
||||
.with_line_number(true)
|
||||
.with_level(true)
|
||||
.with_thread_names(true)
|
||||
.with_span_events(FmtSpan::FULL)
|
||||
.pretty()
|
||||
)
|
||||
.with(fmt_layer)
|
||||
.with(filter)
|
||||
.with(tracing_error::ErrorLayer::default())
|
||||
.try_init() {
|
||||
|
|
@ -32,7 +36,6 @@ pub async fn start_logger() {
|
|||
use chrono::Local;
|
||||
use std::fs::OpenOptions;
|
||||
use tracing_subscriber::fmt::time::ChronoLocal;
|
||||
use tracing_subscriber::prelude::*;
|
||||
|
||||
use crate::io::Dirs;
|
||||
|
||||
|
|
@ -61,14 +64,18 @@ pub async fn start_logger() {
|
|||
let filter = tracing_subscriber::EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new(format!("{}=info", env!("CARGO_PKG_NAME"))));
|
||||
|
||||
let mut fmt_layer = tracing_subscriber::fmt::layer()
|
||||
.compact()
|
||||
.with_writer(file)
|
||||
.with_ansi(false)
|
||||
.with_timer(ChronoLocal::rfc_3339());
|
||||
|
||||
if let Some(span) = Core::get().span_formatting.clone() {
|
||||
fmt_layer = fmt_layer.with_span_events(span);
|
||||
}
|
||||
|
||||
tracing_subscriber::registry()
|
||||
.with(
|
||||
tracing_subscriber::fmt::layer()
|
||||
.compact()
|
||||
.with_writer(file)
|
||||
.with_ansi(false)
|
||||
.with_timer(ChronoLocal::rfc_3339()),
|
||||
)
|
||||
.with(fmt_layer)
|
||||
.with(filter)
|
||||
.with(tracing_error::ErrorLayer::default())
|
||||
.init();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use std::{ops::Deref, sync::Arc};
|
||||
|
||||
use tokio::sync::OnceCell;
|
||||
use tracing_subscriber::fmt::format::FmtSpan;
|
||||
|
||||
use crate::error::{LauncherError, LauncherResult};
|
||||
|
||||
|
|
@ -14,7 +15,8 @@ pub struct CoreOptions {
|
|||
pub launcher_name: String,
|
||||
pub launcher_version: String,
|
||||
pub launcher_website: String,
|
||||
pub fetch_attempts: u8,
|
||||
pub fetch_attempts: usize,
|
||||
pub span_formatting: Option<FmtSpan>,
|
||||
/// Default client id is the same as the one used by the official launcher
|
||||
pub msa_client_id: String,
|
||||
pub msa_redirect_uri: String,
|
||||
|
|
@ -29,6 +31,7 @@ impl Default for CoreOptions {
|
|||
launcher_version: String::from(env!("CARGO_PKG_VERSION")),
|
||||
launcher_website: String::from("https://polyfrost.org/"),
|
||||
fetch_attempts: 3,
|
||||
span_formatting: None,
|
||||
msa_client_id: String::from("00000000402b5328"),
|
||||
msa_redirect_uri: String::from("https://login.live.com/oauth20_desktop.srf"),
|
||||
curseforge_api_key: None,
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ impl Dirs {
|
|||
}
|
||||
|
||||
macro_rules! dirs_impl {
|
||||
($($name:ident => |$self:ident| $path:expr),*) => {
|
||||
($($name:ident => |$self:ident| $path:expr),*$(,)?) => {
|
||||
paste::paste! {
|
||||
impl Dirs {
|
||||
$(
|
||||
|
|
@ -75,9 +75,14 @@ dirs_impl! {
|
|||
clusters_dir => |this| this.base_dir.join("clusters"),
|
||||
metadata_dir => |this| this.base_dir.join("metadata"),
|
||||
|
||||
version_infos_dir => |this| this.metadata_dir().join("version_infos"),
|
||||
versions_dir => |this| this.metadata_dir().join("versions"),
|
||||
libraries_dir => |this| this.metadata_dir().join("libraries"),
|
||||
natives_dir => |this| this.metadata_dir().join("natives"),
|
||||
java_dir => |this| this.metadata_dir().join("java"),
|
||||
caches_dir => |this| this.metadata_dir().join("caches")
|
||||
caches_dir => |this| this.metadata_dir().join("caches"),
|
||||
|
||||
assets_dir => |this| this.metadata_dir().join("assets"),
|
||||
legacy_assets_dir => |this| this.metadata_dir().join("resources"),
|
||||
assets_index_dir => |this| this.assets_dir().join("indexes"),
|
||||
assets_object_dir => |this| this.assets_dir().join("objects"),
|
||||
}
|
||||
|
|
@ -125,16 +125,19 @@ impl Metadata {
|
|||
macro_rules! check {
|
||||
($var:tt, $is_modded:tt) => {
|
||||
if self.inner.$var.is_none() {
|
||||
if let Ok(loader) = GameLoader::from_str(stringify!($var)) {
|
||||
match check!(_fetch, loader, $is_modded) {
|
||||
Ok(data) => {
|
||||
self.inner.$var = Some(data);
|
||||
changed += 1;
|
||||
match GameLoader::from_str(stringify!($var)) {
|
||||
Ok(loader) => {
|
||||
match check!(_fetch, loader, $is_modded) {
|
||||
Ok(data) => {
|
||||
self.inner.$var = Some(data);
|
||||
changed += 1;
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::error!("failed to fetch manifest for {}: {}", loader, err);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::error!("failed to fetch manifest for {}: {}", loader, err);
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(err) => tracing::error!("{err}")
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use tokio::sync::{OnceCell, Semaphore};
|
|||
static STORE_STATE: OnceCell<Arc<SemaphoreStore>> = OnceCell::const_new();
|
||||
|
||||
pub struct SemaphoreStore {
|
||||
fetch: Arc<Semaphore>
|
||||
pub fetch: Arc<Semaphore>
|
||||
}
|
||||
|
||||
impl SemaphoreStore {
|
||||
|
|
|
|||
|
|
@ -60,10 +60,13 @@ pub async fn fetch_advanced(
|
|||
ingress_ref: Option<&IngressRef<'_>>,
|
||||
) -> LauncherResult<Bytes> {
|
||||
let semaphore = SemaphoreStore::fetch().await;
|
||||
let _permit = semaphore.acquire().await?;
|
||||
let permit = semaphore.acquire().await;
|
||||
|
||||
let client = &REQWEST_CLIENT;
|
||||
|
||||
for attempt in 0..Core::get().fetch_attempts {
|
||||
let mut attempt = 0;
|
||||
let res = loop {
|
||||
attempt += 1;
|
||||
let mut req = client.request(method.clone(), url);
|
||||
|
||||
if let Some(body) = body.clone() {
|
||||
|
|
@ -76,67 +79,64 @@ pub async fn fetch_advanced(
|
|||
}
|
||||
}
|
||||
|
||||
let res = req.send().await;
|
||||
|
||||
match res {
|
||||
Err(_) if attempt <= 3 => {}
|
||||
Err(err) => return Err(err.into()),
|
||||
Ok(res) => {
|
||||
let bytes = if let Some(ingress_ref) = ingress_ref {
|
||||
let length = res.content_length();
|
||||
if let Some(total_size) = length {
|
||||
let mut stream = res.bytes_stream();
|
||||
let mut bytes = Vec::new();
|
||||
|
||||
while let Some(item) = stream.next().await {
|
||||
let chunk =
|
||||
item.or(Err(anyhow::anyhow!("no value for fetch bytes")))?;
|
||||
bytes.append(&mut chunk.to_vec());
|
||||
|
||||
send_ingress(
|
||||
ingress_ref,
|
||||
(chunk.len() as f64 / total_size as f64) * ingress_ref.increment_by,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(Bytes::from(bytes))
|
||||
} else {
|
||||
res.bytes().await
|
||||
}
|
||||
} else {
|
||||
res.bytes().await
|
||||
};
|
||||
|
||||
if let Ok(bytes) = bytes {
|
||||
if let Some((ref algorithm, expected_hash)) = hash {
|
||||
let expected_hash = expected_hash.to_string();
|
||||
let calculated_hash = algorithm.hash(&bytes);
|
||||
|
||||
if *calculated_hash != expected_hash {
|
||||
if attempt <= 3 {
|
||||
continue;
|
||||
}
|
||||
|
||||
return Err(CryptoError::InvalidHash {
|
||||
algorithm: algorithm.clone(),
|
||||
expected: expected_hash,
|
||||
actual: calculated_hash,
|
||||
}
|
||||
.into());
|
||||
}
|
||||
}
|
||||
|
||||
tracing::debug!("finished downloading {url}");
|
||||
return Ok(bytes);
|
||||
} else if let Err(err) = bytes {
|
||||
return Err(err.into());
|
||||
match req.send().await {
|
||||
Err(err) => {
|
||||
if attempt <= Core::get().fetch_attempts {
|
||||
tracing::error!("error occurred whilst fetching on attempt {attempt}: {err} retrying request...");
|
||||
continue;
|
||||
}
|
||||
|
||||
return Err(err.into())
|
||||
},
|
||||
Ok(res) => break res
|
||||
}
|
||||
};
|
||||
|
||||
// Drop the fetch permit as we are no longer fetching
|
||||
drop(permit);
|
||||
|
||||
let bytes = if let Some(ingress_ref) = ingress_ref {
|
||||
let length = res.content_length();
|
||||
if let Some(total_size) = length {
|
||||
let mut stream = res.bytes_stream();
|
||||
let mut bytes = Vec::new();
|
||||
|
||||
while let Some(item) = stream.next().await {
|
||||
let chunk =
|
||||
item.or(Err(anyhow::anyhow!("no value for fetch bytes")))?;
|
||||
bytes.append(&mut chunk.to_vec());
|
||||
|
||||
send_ingress(
|
||||
ingress_ref,
|
||||
(chunk.len() as f64 / total_size as f64) * ingress_ref.increment_by,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(Bytes::from(bytes))
|
||||
} else {
|
||||
res.bytes().await
|
||||
}
|
||||
} else {
|
||||
res.bytes().await
|
||||
}?;
|
||||
|
||||
if let Some((ref algorithm, expected_hash)) = hash {
|
||||
let expected_hash = expected_hash.to_string();
|
||||
let calculated_hash = algorithm.hash(&bytes);
|
||||
|
||||
if *calculated_hash != expected_hash {
|
||||
return Err(CryptoError::InvalidHash {
|
||||
algorithm: algorithm.clone(),
|
||||
expected: expected_hash,
|
||||
actual: calculated_hash,
|
||||
}
|
||||
.into());
|
||||
}
|
||||
}
|
||||
|
||||
unreachable!()
|
||||
tracing::debug!("finished downloading {url}");
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
pub async fn fetch(method: Method, url: &str) -> LauncherResult<Bytes> {
|
||||
|
|
@ -147,17 +147,17 @@ pub async fn fetch_json<T: for<'de> Deserialize<'de>>(
|
|||
method: Method,
|
||||
url: &str,
|
||||
body: Option<serde_json::Value>,
|
||||
headers: Option<HashMap<&str, &str>>,
|
||||
ingress_ref: Option<&IngressRef<'_>>,
|
||||
) -> LauncherResult<T> {
|
||||
fetch_json_advanced(method, url, body, None, headers, None).await
|
||||
fetch_json_advanced(method, url, body, None, None, ingress_ref).await
|
||||
}
|
||||
|
||||
pub async fn fetch_json_advanced<T: for<'de> Deserialize<'de>>(
|
||||
method: Method,
|
||||
url: &str,
|
||||
body: Option<serde_json::Value>,
|
||||
hash: Option<(HashAlgorithm, &str)>,
|
||||
headers: Option<HashMap<&str, &str>>,
|
||||
hash: Option<(HashAlgorithm, &str)>,
|
||||
ingress: Option<&IngressRef<'_>>,
|
||||
) -> LauncherResult<T> {
|
||||
let bytes = fetch_advanced(method, url, body, hash, headers, ingress).await?;
|
||||
|
|
@ -169,10 +169,10 @@ pub async fn download_advanced(
|
|||
url: &str,
|
||||
path: impl AsRef<Path>,
|
||||
body: Option<serde_json::Value>,
|
||||
hash: Option<(HashAlgorithm, &str)>,
|
||||
headers: Option<HashMap<&str, &str>>,
|
||||
hash: Option<(HashAlgorithm, &str)>,
|
||||
ingress: Option<&IngressRef<'_>>,
|
||||
) -> LauncherResult<()> {
|
||||
) -> LauncherResult<Bytes> {
|
||||
let bytes = fetch_advanced(
|
||||
method,
|
||||
url,
|
||||
|
|
@ -198,7 +198,7 @@ pub async fn download_advanced(
|
|||
send_ingress(ingress_ref, ingress_ref.increment_by / 4.0).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
pub async fn download(
|
||||
|
|
@ -206,6 +206,7 @@ pub async fn download(
|
|||
url: &str,
|
||||
path: impl AsRef<Path>,
|
||||
hash: Option<(HashAlgorithm, &str)>,
|
||||
) -> LauncherResult<()> {
|
||||
download_advanced(method, url, path, None, hash, None, None).await
|
||||
ingress_ref: Option<&IngressRef<'_>>
|
||||
) -> LauncherResult<Bytes> {
|
||||
download_advanced(method, url, path, None, None, hash, ingress_ref).await
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,14 +8,13 @@ use serde::{Deserialize, Serialize};
|
|||
#[serde(rename_all = "lowercase")]
|
||||
#[sea_orm(rs_type = "u8", db_type = "Integer")]
|
||||
pub enum GameLoader {
|
||||
Vanilla = 0,
|
||||
#[default]
|
||||
Unknown = 1,
|
||||
Forge = 2,
|
||||
NeoForge = 3,
|
||||
Quilt = 4,
|
||||
Fabric = 5,
|
||||
LegacyFabric = 6,
|
||||
Vanilla = 0,
|
||||
Forge = 1,
|
||||
NeoForge = 2,
|
||||
Quilt = 3,
|
||||
Fabric = 4,
|
||||
LegacyFabric = 5,
|
||||
}
|
||||
|
||||
impl GameLoader {
|
||||
|
|
@ -27,7 +26,7 @@ impl GameLoader {
|
|||
|
||||
pub const fn get_format_version(&self) -> usize {
|
||||
match self {
|
||||
Self::Vanilla | Self::Unknown => interpulse::api::minecraft::CURRENT_FORMAT_VERSION,
|
||||
Self::Vanilla => interpulse::api::minecraft::CURRENT_FORMAT_VERSION,
|
||||
Self::Forge => interpulse::api::modded::CURRENT_FORGE_FORMAT_VERSION,
|
||||
Self::NeoForge => interpulse::api::modded::CURRENT_NEOFORGE_FORMAT_VERSION,
|
||||
Self::Quilt => interpulse::api::modded::CURRENT_QUILT_FORMAT_VERSION,
|
||||
|
|
@ -38,7 +37,7 @@ impl GameLoader {
|
|||
|
||||
pub fn get_format_name(&self) -> String {
|
||||
match self {
|
||||
Self::Vanilla | Self::Unknown => String::from("minecraft"),
|
||||
Self::Vanilla => String::from("minecraft"),
|
||||
Self::NeoForge => String::from("neo"), // TODO(metadata): change to neoforge
|
||||
_ => self.to_string().to_lowercase().replace(['_', '.', ' '], "")
|
||||
}
|
||||
|
|
@ -46,25 +45,27 @@ impl GameLoader {
|
|||
|
||||
}
|
||||
|
||||
impl From<String> for GameLoader {
|
||||
fn from(s: String) -> Self {
|
||||
match s.to_lowercase().replace(['_', '.', ' '], "").as_str() {
|
||||
impl TryFrom<String> for GameLoader {
|
||||
type Error = String;
|
||||
|
||||
fn try_from(s: String) -> Result<GameLoader, String> {
|
||||
Ok(match s.to_lowercase().replace(['_', '.', ' '], "").as_str() {
|
||||
"vanilla" => Self::Vanilla,
|
||||
"forge" => Self::Forge,
|
||||
"neoforge" => Self::NeoForge,
|
||||
"neoforge" | "neo" => Self::NeoForge,
|
||||
"quilt" => Self::Quilt,
|
||||
"fabric" => Self::Fabric,
|
||||
"legacyfabric" => Self::LegacyFabric,
|
||||
_ => Self::Unknown,
|
||||
}
|
||||
_ => return Err(format!("'{s}' is not a valid game loader")),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for GameLoader {
|
||||
type Err = ();
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Self::from(s.to_string()))
|
||||
Self::try_from(s.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -78,7 +79,6 @@ impl Display for GameLoader {
|
|||
Self::Quilt => "Quilt",
|
||||
Self::Fabric => "Fabric",
|
||||
Self::LegacyFabric => "LegacyFabric",
|
||||
Self::Unknown => "Unknown",
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue