From f000ba9bdfd63aac3bc770a3c509d8b359371c7a Mon Sep 17 00:00:00 2001
From: LynithDev <61880709+LynithDev@users.noreply.github.com>
Date: Sat, 30 Mar 2024 19:40:59 +0100
Subject: [PATCH] feat: Lots of changes
---
.idea/.gitignore | 8 +
.idea/Launcher.iml | 12 +
.idea/discord.xml | 7 +
.idea/inspectionProfiles/Project_Default.xml | 6 +
.idea/modules.xml | 8 +
.idea/vcs.xml | 6 +
onelauncher/src/constants.rs | 36 ++-
onelauncher/src/error.rs | 4 +
onelauncher/src/game/client.rs | 23 +-
onelauncher/src/game/client_manager.rs | 11 +-
onelauncher/src/game/clients/mod.rs | 8 +-
onelauncher/src/game/clients/vanilla.rs | 161 ++++++----
onelauncher/src/game/minecraft.rs | 124 ++++++--
onelauncher/src/utils/file.rs | 9 +-
pnpm-lock.yaml | 312 +++++--------------
src/ui/components/game/PlayerHead.tsx | 2 +-
16 files changed, 395 insertions(+), 342 deletions(-)
create mode 100644 .idea/.gitignore
create mode 100644 .idea/Launcher.iml
create mode 100644 .idea/discord.xml
create mode 100644 .idea/inspectionProfiles/Project_Default.xml
create mode 100644 .idea/modules.xml
create mode 100644 .idea/vcs.xml
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 00000000..13566b81
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/Launcher.iml b/.idea/Launcher.iml
new file mode 100644
index 00000000..ba207acf
--- /dev/null
+++ b/.idea/Launcher.iml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/discord.xml b/.idea/discord.xml
new file mode 100644
index 00000000..d8e95616
--- /dev/null
+++ b/.idea/discord.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 00000000..03d9549e
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 00000000..24e7e3a1
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 00000000..35eb1ddf
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/onelauncher/src/constants.rs b/onelauncher/src/constants.rs
index fad91da7..5b1cc774 100644
--- a/onelauncher/src/constants.rs
+++ b/onelauncher/src/constants.rs
@@ -2,16 +2,44 @@
pub const NAME: &str = "OneLauncher";
pub const VERSION: &str = "0.1.0";
+
// Authentication and HTTP (TODO: env?)
pub const APP_CONFIG_DIR: &str = "org.polyfrost.launcher";
pub const USER_AGENT: &str = "OneLauncher/1.0.0 (polyfrost.org)";
+
// Microsoft Authentication
pub const CLIENT_ID: &str = "9419b7ee-1448-4d1b-b52a-550d8f36ab56";
pub const MSA_PORT: u16 = 13523;
-// Minecraft
-pub const MINECRAFT_VERSIONS_MANIFEST: &str =
- "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json";
-// Java Downloading
+// Minecraft
+pub const MINECRAFT_VERSIONS_MANIFEST: &str = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json";
+
+
+// OS constants which match Minecraft's/Mojang's scheme
+#[cfg(target_os = "windows")]
+pub const TARGET_OS: &str = "windows";
+
+#[cfg(target_os = "macos")]
+pub const TARGET_OS: &str = "osx";
+
+#[cfg(target_os = "linux")]
+pub const TARGET_OS: &str = "linux";
+
+#[cfg(target_arch = "x86")]
+pub const NATIVE_ARCH: &str = "32";
+
+#[cfg(target_arch = "x86_64")]
+pub const NATIVE_ARCH: &str = "64";
+
+// TODO: Add more architectures
+#[cfg(all(not(target_arch = "x86_64"), not(target_arch = "x86")))]
+pub const NATIVE_ARCH: &str = "64";
+
+// Minecraft library splitter
+#[cfg(target_os = "windows")]
+pub const LIBRARY_SPLITTER: &str = ";";
+
+#[cfg(not(target_os = "windows"))]
+pub const LIBRARY_SPLITTER: &str = ":";
diff --git a/onelauncher/src/error.rs b/onelauncher/src/error.rs
index ad5812a6..e5b478b4 100644
--- a/onelauncher/src/error.rs
+++ b/onelauncher/src/error.rs
@@ -66,6 +66,10 @@ pub enum ErrorKind {
/// Wrapper around [`crate::game::client::ClientManagerError`] to handle directory errors.
#[error("failed to manage clients: {0}")]
ClientManagerError(#[from] crate::game::client_manager::ClientManagerError),
+
+ /// Wrapper around [`reqwest::header::ToStrError`] to handle header conversion errors.
+ #[error("failed to convert header to string: {0}")]
+ HeaderToStrError(#[from] reqwest::header::ToStrError),
}
#[derive(Debug)]
diff --git a/onelauncher/src/game/client.rs b/onelauncher/src/game/client.rs
index 79e01c26..1b3c6160 100644
--- a/onelauncher/src/game/client.rs
+++ b/onelauncher/src/game/client.rs
@@ -1,27 +1,15 @@
-use std::path::PathBuf;
-
use async_trait::async_trait;
use chrono::{DateTime, Local};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
-use super::{clients::ClientType, minecraft::{MinecraftManifest, ReleaseType}};
-
-#[derive(Debug, Clone, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct MinecraftVersion {
- pub id: String,
- pub url: String,
- #[serde(default)]
- pub release_type: ReleaseType,
- #[serde(default)]
- pub release_time: chrono::DateTime,
-}
+use super::{clients::ClientType, minecraft::MinecraftManifest};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Manifest {
pub id: Uuid,
- pub manifest: MinecraftManifest,
+ #[serde(rename = "manifest")]
+ pub minecraft_manifest: MinecraftManifest,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -44,11 +32,6 @@ pub trait ClientTrait<'a>: Send + Sync {
async fn launch(&self) -> crate::Result<()>;
async fn setup(&self) -> crate::Result<()>;
- async fn install_game(&self) -> crate::Result;
- async fn install_libraries(&self) -> crate::Result;
- async fn install_natives(&self) -> crate::Result<()>;
- async fn install_assets(&self) -> crate::Result<()>;
-
fn get_cluster(&self) -> &'a Cluster
where
Self: Sized;
diff --git a/onelauncher/src/game/client_manager.rs b/onelauncher/src/game/client_manager.rs
index 1aa42388..2f67d3bb 100644
--- a/onelauncher/src/game/client_manager.rs
+++ b/onelauncher/src/game/client_manager.rs
@@ -13,6 +13,7 @@ use super::{client::{ClientTrait, Cluster, Manifest}, clients::{self, vanilla, C
pub enum ClientManagerError {
#[error("Cluster not found")]
ClusterNotFound,
+
#[error("Manifest not found")]
ManifestNotFound,
}
@@ -24,13 +25,9 @@ pub struct ClientManager {
impl ClientManager {
pub fn new() -> crate::Result {
- let clusters = load_and_serialize::(&dirs::clusters_dir()?)?;
-
- let manifests = load_and_serialize::(&dirs::manifests_dir()?)?;
-
Ok(Self {
- clusters,
- manifests,
+ clusters: load_and_serialize::(&dirs::clusters_dir()?)?,
+ manifests: load_and_serialize::(&dirs::manifests_dir()?)?,
})
}
@@ -108,7 +105,7 @@ impl ClientManager {
let minecraft_manifest = vanilla::retrieve_version_manifest(url).await?;
let manifest = Manifest {
id: uuid,
- manifest: minecraft_manifest,
+ minecraft_manifest,
};
save(
diff --git a/onelauncher/src/game/clients/mod.rs b/onelauncher/src/game/clients/mod.rs
index 1f42aef6..7faf775b 100644
--- a/onelauncher/src/game/clients/mod.rs
+++ b/onelauncher/src/game/clients/mod.rs
@@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize};
use self::vanilla::VanillaClientProps;
-use super::client::{ClientTrait, Cluster, Manifest, MinecraftVersion};
+use super::{client::{ClientTrait, Cluster, Manifest}, minecraft::MinecraftVersion};
pub mod vanilla;
@@ -14,6 +14,12 @@ pub enum ClientType {
Vanilla(VanillaClientProps),
}
+#[derive(Debug)]
+pub struct LaunchInfo {
+ // This is where things like account info, features etc will go
+ pub java: PathBuf
+}
+
pub async fn get_versions(client: &ClientType, file: Option<&PathBuf>) -> crate::Result> {
match client {
ClientType::Vanilla(_) => vanilla::get_versions(file).await,
diff --git a/onelauncher/src/game/clients/vanilla.rs b/onelauncher/src/game/clients/vanilla.rs
index 70362414..96518591 100644
--- a/onelauncher/src/game/clients/vanilla.rs
+++ b/onelauncher/src/game/clients/vanilla.rs
@@ -1,7 +1,7 @@
use crate::{
- constants::MINECRAFT_VERSIONS_MANIFEST,
+ constants::{self, MINECRAFT_VERSIONS_MANIFEST},
create_game_client,
- game::{client::{ClientTrait, Cluster, Manifest, MinecraftVersion}, minecraft::MinecraftManifest},
+ game::{client::{ClientTrait, Cluster, Manifest}, minecraft::{Library, MinecraftManifest, MinecraftVersion, RuleListExt}},
impl_game_client,
utils::{dirs, http}
};
@@ -10,9 +10,12 @@ use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::{fs, path::PathBuf};
+pub fn vanilla_dir() -> crate::Result {
+ Ok(dirs::clients_dir()?.join("vanilla"))
+}
+
create_game_client! {
VanillaClientProps {}
-
VanillaClient {}
}
@@ -29,38 +32,94 @@ impl<'a> ClientTrait<'a> for VanillaClient<'a> {
}
async fn setup(&self) -> crate::Result<()> {
- let client_path = self.install_game().await?;
- println!("{:?}", client_path);
- Ok(())
- }
+ let manifest = &self.manifest.minecraft_manifest;
- async fn install_game(&self) -> crate::Result {
- let manifest = &self.manifest.manifest;
- let file = dirs::clients_dir()?.join(format!("{}.jar", manifest.version));
-
- if !file.exists() {
- fs::create_dir_all(&file)?;
- let artifact = &manifest.downloads.client;
-
- http::download_file_sha1_check(&artifact.url, &file, &artifact.sha1).await?;
- }
-
- Ok(file)
- }
-
- async fn install_libraries(&self) -> crate::Result {
- Ok("".to_string())
- }
-
- async fn install_natives(&self) -> crate::Result<()> {
- Ok(())
- }
-
- async fn install_assets(&self) -> crate::Result<()> {
+ let client_path = install_game(manifest).await?;
+ let libraries = install_libraries(manifest).await?;
Ok(())
}
}
+async fn install_game(manifest: &MinecraftManifest) -> crate::Result {
+ let file_name = format!("{}.jar", manifest.version);
+ let file = vanilla_dir()?.join(file_name);
+
+ if !file.exists() {
+ fs::create_dir_all(&file.parent().ok_or(anyhow!("Couldn't take client jar parent folder"))?)?;
+
+ let artifact = &manifest.downloads.client;
+ http::download_file_sha1_check(&artifact.url, &file, &artifact.sha1).await?;
+ }
+
+ Ok(file)
+}
+
+pub async fn install_libraries(manifest: &MinecraftManifest) -> crate::Result {
+ let libraries = &manifest.libraries;
+
+ let mut natives_ret: Vec<&Library> = vec![];
+ let mut libraries_ret: Vec = vec![];
+
+ for library in libraries {
+ if !&library.rules.check() {
+ continue;
+ }
+
+ if let Some(_) = library.natives {
+ natives_ret.push(library);
+ continue;
+ }
+
+ let artifact = library.downloads.artifact.clone().ok_or(anyhow!("No artifact object"))?;
+ let path = artifact.path;
+ let url = artifact.url;
+
+ let dest = dirs::libraries_dir()?.join(path);
+ fs::create_dir_all(dest.parent().ok_or(anyhow!("Couldn't get library parent"))?)?;
+
+ if !dest.exists() {
+ http::download_file_sha1_check(url.as_str(), &dest, &artifact.sha1).await?;
+ }
+
+ libraries_ret.push(dest.to_str().unwrap().to_string());
+ }
+
+ install_natives(natives_ret).await?;
+ Ok(libraries_ret.join(constants::LIBRARY_SPLITTER))
+}
+
+pub async fn install_natives(natives: Vec<&Library>) -> crate::Result<()> {
+ for native in natives {
+ let classifiers = native.natives.as_ref().unwrap();
+ let classifier = match classifiers.get(constants::TARGET_OS) {
+ Some(classifier) => classifier.replace("${arch}", constants::NATIVE_ARCH),
+ None => continue,
+ };
+
+ let artifact = native.downloads.classifiers.as_ref()
+ .ok_or(anyhow!("No classifiers object"))?
+ .get(&classifier)
+ .ok_or(anyhow!("No classifier object"))?;
+
+ let path = artifact.path.clone();
+ let url = artifact.url.clone();
+
+ let dest = dirs::libraries_dir()?.join(path);
+ fs::create_dir_all(dest.parent().ok_or(anyhow!("Couldn't get native parent"))?)?;
+
+ if !dest.exists() {
+ http::download_file_sha1_check(url.as_str(), &dest, &artifact.sha1).await?;
+ }
+ }
+
+ Ok(())
+}
+
+pub async fn install_assets() -> crate::Result<()> {
+ Ok(())
+}
+
+
#[derive(Clone, Serialize, Deserialize)]
struct VersionList {
versions: Vec,
@@ -75,9 +134,22 @@ struct CachedVersions {
pub async fn get_versions(file: Option<&PathBuf>) -> crate::Result> {
if let Some(file) = file {
if file.exists() && file.is_file() {
- if let Some(versions) = get_cached_versions(file).await? {
- return Ok(versions);
- }
+ let cached = serde_json::from_str::(&fs::read_to_string(file)?)?;
+ let head_request = http::create_client()?
+ .head(MINECRAFT_VERSIONS_MANIFEST)
+ .send()
+ .await?;
+
+ let last_updated = head_request
+ .headers()
+ .get("Last-Modified")
+ .ok_or(anyhow!("Last-Modified header not found"))?;
+
+ let last_updated = chrono::DateTime::parse_from_rfc2822(last_updated.to_str()?)?;
+
+ if cached.last_updated > last_updated.timestamp() {
+ return Ok(cached.versions);
+ }
}
}
@@ -100,29 +172,6 @@ pub async fn get_versions(file: Option<&PathBuf>) -> crate::Result crate::Result