You've already forked AstralRinth
forked from didirus/AstralRinth
Merge commit '74cf3f076eff43755bb4bef62f1c1bb3fc0e6c2a' into feature-clean
This commit is contained in:
@@ -1,14 +1,14 @@
|
||||
//! Functions for fetching infromation from the Internet
|
||||
//! Functions for fetching information from the Internet
|
||||
use super::io::{self, IOError};
|
||||
use crate::config::{MODRINTH_API_URL, MODRINTH_API_URL_V3};
|
||||
use crate::event::emit::emit_loading;
|
||||
use crate::event::LoadingBarId;
|
||||
use crate::event::emit::emit_loading;
|
||||
use bytes::Bytes;
|
||||
use lazy_static::lazy_static;
|
||||
use reqwest::Method;
|
||||
use serde::de::DeserializeOwned;
|
||||
use std::ffi::OsStr;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::LazyLock;
|
||||
use std::time::{self};
|
||||
use tokio::sync::Semaphore;
|
||||
use tokio::{fs::File, io::AsyncWriteExt};
|
||||
@@ -18,22 +18,20 @@ pub struct IoSemaphore(pub Semaphore);
|
||||
#[derive(Debug)]
|
||||
pub struct FetchSemaphore(pub Semaphore);
|
||||
|
||||
lazy_static! {
|
||||
pub static ref REQWEST_CLIENT: reqwest::Client = {
|
||||
let mut headers = reqwest::header::HeaderMap::new();
|
||||
let header = reqwest::header::HeaderValue::from_str(&format!(
|
||||
"modrinth/theseus/{} (support@modrinth.com)",
|
||||
env!("CARGO_PKG_VERSION")
|
||||
))
|
||||
.unwrap();
|
||||
headers.insert(reqwest::header::USER_AGENT, header);
|
||||
reqwest::Client::builder()
|
||||
.tcp_keepalive(Some(time::Duration::from_secs(10)))
|
||||
.default_headers(headers)
|
||||
.build()
|
||||
.expect("Reqwest Client Building Failed")
|
||||
};
|
||||
}
|
||||
pub static REQWEST_CLIENT: LazyLock<reqwest::Client> = LazyLock::new(|| {
|
||||
let mut headers = reqwest::header::HeaderMap::new();
|
||||
let header = reqwest::header::HeaderValue::from_str(&format!(
|
||||
"modrinth/theseus/{} (support@modrinth.com)",
|
||||
env!("CARGO_PKG_VERSION")
|
||||
))
|
||||
.unwrap();
|
||||
headers.insert(reqwest::header::USER_AGENT, header);
|
||||
reqwest::Client::builder()
|
||||
.tcp_keepalive(Some(time::Duration::from_secs(10)))
|
||||
.default_headers(headers)
|
||||
.build()
|
||||
.expect("Reqwest Client Building Failed")
|
||||
});
|
||||
const FETCH_ATTEMPTS: usize = 3;
|
||||
|
||||
#[tracing::instrument(skip(semaphore))]
|
||||
|
||||
@@ -255,3 +255,42 @@ pub async fn remove_file(
|
||||
path: path.to_string_lossy().to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
// open file
|
||||
pub async fn open_file(
|
||||
path: impl AsRef<std::path::Path>,
|
||||
) -> Result<tokio::fs::File, IOError> {
|
||||
let path = path.as_ref();
|
||||
tokio::fs::File::open(path)
|
||||
.await
|
||||
.map_err(|e| IOError::IOPathError {
|
||||
source: e,
|
||||
path: path.to_string_lossy().to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
// remove dir
|
||||
pub async fn remove_dir(
|
||||
path: impl AsRef<std::path::Path>,
|
||||
) -> Result<(), IOError> {
|
||||
let path = path.as_ref();
|
||||
tokio::fs::remove_dir(path)
|
||||
.await
|
||||
.map_err(|e| IOError::IOPathError {
|
||||
source: e,
|
||||
path: path.to_string_lossy().to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
// metadata
|
||||
pub async fn metadata(
|
||||
path: impl AsRef<std::path::Path>,
|
||||
) -> Result<std::fs::Metadata, IOError> {
|
||||
let path = path.as_ref();
|
||||
tokio::fs::metadata(path)
|
||||
.await
|
||||
.map_err(|e| IOError::IOPathError {
|
||||
source: e,
|
||||
path: path.to_string_lossy().to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@ use tokio::task::JoinError;
|
||||
use crate::State;
|
||||
#[cfg(target_os = "windows")]
|
||||
use winreg::{
|
||||
enums::{HKEY_LOCAL_MACHINE, KEY_READ, KEY_WOW64_32KEY, KEY_WOW64_64KEY},
|
||||
RegKey,
|
||||
enums::{HKEY_LOCAL_MACHINE, KEY_READ, KEY_WOW64_32KEY, KEY_WOW64_64KEY},
|
||||
};
|
||||
|
||||
// Entrypoint function (Windows)
|
||||
@@ -276,11 +276,10 @@ pub async fn check_java_at_filepath(path: &Path) -> Option<JavaVersion> {
|
||||
};
|
||||
|
||||
let bytes = include_bytes!("../../library/JavaInfo.class");
|
||||
let tempdir: PathBuf = tempfile::tempdir().ok()?.into_path();
|
||||
if !tempdir.exists() {
|
||||
let Ok(tempdir) = tempfile::tempdir() else {
|
||||
return None;
|
||||
}
|
||||
let file_path = tempdir.join("JavaInfo.class");
|
||||
};
|
||||
let file_path = tempdir.path().join("JavaInfo.class");
|
||||
io::write(&file_path, bytes).await.ok()?;
|
||||
|
||||
let output = Command::new(&java)
|
||||
|
||||
@@ -3,7 +3,8 @@ pub mod fetch;
|
||||
pub mod io;
|
||||
pub mod jre;
|
||||
pub mod platform;
|
||||
pub mod utils;
|
||||
pub mod utils; // AstralRinth
|
||||
pub mod server_ping;
|
||||
|
||||
/// Wrap a builder which uses a mut reference into one which outputs an owned value
|
||||
macro_rules! wrap_ref_builder {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
//! Platform-related code
|
||||
use daedalus::minecraft::{Os, OsRule};
|
||||
use regex::Regex;
|
||||
|
||||
// OS detection
|
||||
pub trait OsExt {
|
||||
@@ -92,12 +91,16 @@ pub fn os_rule(
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(version) = &rule.version {
|
||||
if let Ok(regex) = Regex::new(version.as_str()) {
|
||||
rule_match &=
|
||||
regex.is_match(&sys_info::os_release().unwrap_or_default());
|
||||
}
|
||||
}
|
||||
// `rule.version` is ignored because it's not usually seen on real recent
|
||||
// Minecraft version manifests, its alleged regex syntax is undefined and is
|
||||
// likely to not match `Regex`'s, and the way to get the value to match it
|
||||
// against is allegedly calling `System.getProperty("os.version")`, which
|
||||
// on Windows the OpenJDK implements by fetching the kernel32.dll version,
|
||||
// an approach that no public Rust library implements. Moreover, launchers
|
||||
// such as PrismLauncher also ignore this field. Code references:
|
||||
// - https://github.com/openjdk/jdk/blob/948ade8e7003a41683600428c8e3155c7ed798db/src/java.base/windows/native/libjava/java_props_md.c#L556
|
||||
// - https://github.com/PrismLauncher/PrismLauncher/blob/1c20faccf88999474af70db098a4c10e7a03af33/launcher/minecraft/Rule.h#L77
|
||||
// - https://github.com/FillZpp/sys-info-rs/blob/60ecf1470a5b7c90242f429934a3bacb6023ec4d/c/windows.c#L23-L38
|
||||
|
||||
rule_match
|
||||
}
|
||||
|
||||
223
packages/app-lib/src/util/server_ping.rs
Normal file
223
packages/app-lib/src/util/server_ping.rs
Normal file
@@ -0,0 +1,223 @@
|
||||
use crate::ErrorKind;
|
||||
use crate::error::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::value::RawValue;
|
||||
use std::time::Duration;
|
||||
use tokio::net::ToSocketAddrs;
|
||||
use tokio::select;
|
||||
use url::Url;
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ServerStatus {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub description: Option<Box<RawValue>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub players: Option<ServerPlayers>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub version: Option<ServerVersion>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub favicon: Option<Url>,
|
||||
#[serde(default)]
|
||||
pub enforces_secure_chat: bool,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub ping: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
pub struct ServerPlayers {
|
||||
pub max: i32,
|
||||
pub online: i32,
|
||||
#[serde(default)]
|
||||
pub sample: Vec<ServerGameProfile>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
pub struct ServerGameProfile {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
pub struct ServerVersion {
|
||||
pub name: String,
|
||||
pub protocol: i32,
|
||||
}
|
||||
|
||||
pub async fn get_server_status(
|
||||
address: &impl ToSocketAddrs,
|
||||
original_address: (&str, u16),
|
||||
protocol_version: Option<i32>,
|
||||
) -> Result<ServerStatus> {
|
||||
select! {
|
||||
res = modern::status(address, original_address, protocol_version) => res,
|
||||
_ = tokio::time::sleep(Duration::from_secs(30)) => Err(ErrorKind::OtherError(
|
||||
format!("Ping of {}:{} timed out", original_address.0, original_address.1)
|
||||
).into())
|
||||
}
|
||||
}
|
||||
|
||||
mod modern {
|
||||
use super::ServerStatus;
|
||||
use crate::ErrorKind;
|
||||
use chrono::Utc;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::net::{TcpStream, ToSocketAddrs};
|
||||
|
||||
pub async fn status(
|
||||
address: &impl ToSocketAddrs,
|
||||
original_address: (&str, u16),
|
||||
protocol_version: Option<i32>,
|
||||
) -> crate::Result<ServerStatus> {
|
||||
let mut stream = TcpStream::connect(address).await?;
|
||||
handshake(&mut stream, original_address, protocol_version).await?;
|
||||
let mut result = status_body(&mut stream).await?;
|
||||
result.ping = ping(&mut stream).await.ok();
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
async fn handshake(
|
||||
stream: &mut TcpStream,
|
||||
original_address: (&str, u16),
|
||||
protocol_version: Option<i32>,
|
||||
) -> crate::Result<()> {
|
||||
let (host, port) = original_address;
|
||||
let protocol_version = protocol_version.unwrap_or(-1);
|
||||
|
||||
const PACKET_ID: i32 = 0;
|
||||
const NEXT_STATE: i32 = 1;
|
||||
|
||||
let packet_size = varint::get_byte_size(PACKET_ID)
|
||||
+ varint::get_byte_size(protocol_version)
|
||||
+ varint::get_byte_size(host.len() as i32)
|
||||
+ host.len()
|
||||
+ size_of::<u16>()
|
||||
+ varint::get_byte_size(NEXT_STATE);
|
||||
|
||||
let mut packet_buffer = Vec::with_capacity(
|
||||
varint::get_byte_size(packet_size as i32) + packet_size,
|
||||
);
|
||||
|
||||
varint::write(&mut packet_buffer, packet_size as i32);
|
||||
varint::write(&mut packet_buffer, PACKET_ID);
|
||||
varint::write(&mut packet_buffer, protocol_version);
|
||||
varint::write(&mut packet_buffer, host.len() as i32);
|
||||
packet_buffer.extend_from_slice(host.as_bytes());
|
||||
packet_buffer.extend_from_slice(&port.to_be_bytes());
|
||||
varint::write(&mut packet_buffer, NEXT_STATE);
|
||||
|
||||
stream.write_all(&packet_buffer).await?;
|
||||
stream.flush().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn status_body(
|
||||
stream: &mut TcpStream,
|
||||
) -> crate::Result<ServerStatus> {
|
||||
stream.write_all(&[0x01, 0x00]).await?;
|
||||
stream.flush().await?;
|
||||
|
||||
let packet_length = varint::read(stream).await?;
|
||||
if packet_length < 0 {
|
||||
return Err(ErrorKind::InputError(
|
||||
"Invalid status response packet length".to_string(),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
let mut packet_stream = stream.take(packet_length as u64);
|
||||
let packet_id = varint::read(&mut packet_stream).await?;
|
||||
if packet_id != 0x00 {
|
||||
return Err(ErrorKind::InputError(
|
||||
"Unexpected status response".to_string(),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
let response_length = varint::read(&mut packet_stream).await?;
|
||||
let mut json_response = vec![0_u8; response_length as usize];
|
||||
packet_stream.read_exact(&mut json_response).await?;
|
||||
|
||||
if packet_stream.limit() > 0 {
|
||||
tokio::io::copy(&mut packet_stream, &mut tokio::io::sink()).await?;
|
||||
}
|
||||
|
||||
Ok(serde_json::from_slice(&json_response)?)
|
||||
}
|
||||
|
||||
async fn ping(stream: &mut TcpStream) -> crate::Result<i64> {
|
||||
let start_time = Utc::now();
|
||||
let ping_magic = start_time.timestamp_millis();
|
||||
|
||||
stream.write_all(&[0x09, 0x01]).await?;
|
||||
stream.write_i64(ping_magic).await?;
|
||||
stream.flush().await?;
|
||||
|
||||
let mut response_prefix = [0_u8; 2];
|
||||
stream.read_exact(&mut response_prefix).await?;
|
||||
let response_magic = stream.read_i64().await?;
|
||||
if response_prefix != [0x09, 0x01] || response_magic != ping_magic {
|
||||
return Err(ErrorKind::InputError(
|
||||
"Unexpected ping response".to_string(),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
let response_time = Utc::now();
|
||||
Ok((response_time - start_time).num_milliseconds())
|
||||
}
|
||||
|
||||
mod varint {
|
||||
use std::io;
|
||||
use tokio::io::{AsyncRead, AsyncReadExt};
|
||||
|
||||
const MAX_VARINT_SIZE: usize = 5;
|
||||
const DATA_BITS_MASK: u32 = 0x7f;
|
||||
const CONT_BIT_MASK_U8: u8 = 0x80;
|
||||
const CONT_BIT_MASK_U32: u32 = CONT_BIT_MASK_U8 as u32;
|
||||
const DATA_BITS_PER_BYTE: usize = 7;
|
||||
|
||||
pub fn get_byte_size(x: i32) -> usize {
|
||||
let x = x as u32;
|
||||
for size in 1..MAX_VARINT_SIZE {
|
||||
if (x & (u32::MAX << (size * DATA_BITS_PER_BYTE))) == 0 {
|
||||
return size;
|
||||
}
|
||||
}
|
||||
MAX_VARINT_SIZE
|
||||
}
|
||||
|
||||
pub fn write(out: &mut Vec<u8>, value: i32) {
|
||||
let mut value = value as u32;
|
||||
while value >= CONT_BIT_MASK_U32 {
|
||||
out.push(((value & DATA_BITS_MASK) | CONT_BIT_MASK_U32) as u8);
|
||||
value >>= DATA_BITS_PER_BYTE;
|
||||
}
|
||||
out.push(value as u8);
|
||||
}
|
||||
|
||||
pub async fn read<R: AsyncRead + Unpin>(
|
||||
reader: &mut R,
|
||||
) -> io::Result<i32> {
|
||||
let mut result = 0;
|
||||
let mut shift = 0;
|
||||
|
||||
loop {
|
||||
let b = reader.read_u8().await?;
|
||||
result |=
|
||||
(b as u32 & DATA_BITS_MASK) << (shift * DATA_BITS_PER_BYTE);
|
||||
shift += 1;
|
||||
if shift > MAX_VARINT_SIZE {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"VarInt too big",
|
||||
));
|
||||
}
|
||||
if b & CONT_BIT_MASK_U8 == 0 {
|
||||
return Ok(result as i32);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::io;
|
||||
|
||||
/*
|
||||
AstralRinth Utils
|
||||
*/
|
||||
const PACKAGE_JSON_CONTENT: &str =
|
||||
// include_str!("../../../../apps/app-frontend/package.json");
|
||||
include_str!("../../../../apps/app/tauri.conf.json");
|
||||
|
||||
Reference in New Issue
Block a user