Downloading launcher files

This commit is contained in:
Jai A
2021-06-29 22:32:52 -07:00
commit 93418edbe7
16 changed files with 1870 additions and 0 deletions

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,222 @@
use crate::launcher::meta::{
Asset, AssetIndex, AssetsIndex, DownloadType, Library, Os, RuleAction, VersionInfo,
};
use futures::future;
use std::fs::File;
use std::io::{BufReader, Write};
use std::path::Path;
pub async fn download_client(client_path: &Path, version_info: &VersionInfo) {
let client = download_file(
&version_info
.downloads
.get(&DownloadType::Client)
.unwrap()
.url,
)
.await;
save_file(
&*client_path
.join(&version_info.id)
.join(format!("{}.jar", &version_info.id)),
&client,
);
save_file(
&*client_path
.join(&version_info.id)
.join(format!("{}.json", &version_info.id)),
&bytes::Bytes::from(serde_json::to_string(version_info).unwrap()),
);
}
pub async fn download_assets(
assets_path: &Path,
legacy_path: Option<&Path>,
meta: AssetIndex,
index: &AssetsIndex,
) {
save_file(
&*assets_path
.join("indexes")
.join(format!("{}.json", meta.id)),
&bytes::Bytes::from(serde_json::to_string(index).unwrap()),
);
future::join_all(
index
.objects
.iter()
.map(|x| download_asset(assets_path, legacy_path, x.0, x.1)),
)
.await;
}
async fn download_asset(
assets_path: &Path,
legacy_path: Option<&Path>,
name: &String,
asset: &Asset,
) {
let sub_hash = &&asset.hash[..2];
let resource = download_file(&format!(
"https://resources.download.minecraft.net/{}/{}",
sub_hash, asset.hash
))
.await;
let resource_path = assets_path.join(sub_hash).join(&asset.hash);
save_file(resource_path.as_path(), &resource);
if let Some(legacy_path) = legacy_path {
let resource_path =
legacy_path.join(name.replace('/', &*std::path::MAIN_SEPARATOR.to_string()));
save_file(resource_path.as_path(), &resource);
}
}
pub async fn download_libraries(libraries_path: &Path, natives_path: &Path, libraries: &[Library]) {
future::join_all(
libraries
.iter()
.map(|x| download_library(libraries_path, natives_path, x)),
)
.await;
}
async fn download_library(libraries_path: &Path, natives_path: &Path, library: &Library) {
if let Some(rules) = &library.rules {
let mut allowed = true;
for rule in rules {
match rule.action {
RuleAction::Allow => {
if let Some(os) = &rule.os {
allowed = os.name == &get_os()
} else {
allowed = true
}
}
RuleAction::Disallow => {
if let Some(os) = &rule.os {
allowed = os.name != &get_os()
} else {
allowed = false
}
}
}
}
if !allowed {
return;
}
}
let name_items = library.name.split(':').collect::<Vec<&str>>();
let package = name_items.get(0).unwrap();
let name = name_items.get(1).unwrap();
let version = name_items.get(2).unwrap();
future::join(
download_library_jar(libraries_path, library, package, name, version),
download_native(
libraries_path,
natives_path,
library,
package,
name,
version,
),
)
.await;
}
async fn download_library_jar(
libraries_path: &Path,
library: &Library,
package: &str,
name: &str,
version: &str,
) {
if let Some(library) = &library.downloads.artifact {
let bytes = download_file(&library.url).await;
save_file(
&libraries_path
.join(package)
.join(name)
.join(version)
.join(format!("{}-{}.jar", name, version)),
&bytes,
);
}
}
async fn download_native(
libraries_path: &Path,
natives_path: &Path,
library: &Library,
package: &str,
name: &str,
version: &str,
) {
if let Some(natives) = &library.natives {
if let Some(os_key) = natives.get(&get_os()) {
if let Some(classifiers) = &library.downloads.classifiers {
#[cfg(target_pointer_width = "64")]
let parsed_key = os_key.replace("${arch}", "64");
#[cfg(target_pointer_width = "32")]
let parsed_key = os_key.replace("${arch}", "32");
if let Some(native) = classifiers.get(&*parsed_key) {
let path = &libraries_path
.join(package)
.join(name)
.join(version)
.join(format!("{}-{}-{}.jar", name, version, parsed_key));
let bytes = download_file(&native.url).await;
save_file(path, &bytes);
let file = File::open(path).unwrap();
let reader = BufReader::new(file);
let mut archive = zip::ZipArchive::new(reader).unwrap();
archive.extract(natives_path).unwrap();
}
}
}
}
}
fn save_file(path: &Path, bytes: &bytes::Bytes) {
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
let mut file = File::create(path).unwrap();
file.write_all(bytes).unwrap();
}
async fn download_file(url: &str) -> bytes::Bytes {
reqwest::Client::builder()
.tcp_keepalive(Some(std::time::Duration::from_secs(10)))
.build()
.unwrap()
.get(url)
.send()
.await
.unwrap()
.bytes()
.await
.unwrap()
}
fn get_os() -> Os {
match std::env::consts::OS {
"windows" => Os::Windows,
"macos" => Os::Osx,
"linux" => Os::Linux,
_ => Os::Unknown,
}
}

View File

@@ -0,0 +1,31 @@
use lazy_static::lazy_static;
use regex::Regex;
use std::process::Command;
#[derive(thiserror::Error, Debug)]
pub enum JavaError {
#[error("System Error")]
SystemError(#[from] std::io::Error),
}
lazy_static! {
static ref JAVA_VERSION_REGEX: Regex = Regex::new(r#""(.*?)""#).unwrap();
}
pub fn check_java() -> Result<Option<String>, JavaError> {
let child = Command::new("/usr/lib/jvm/java-8-openjdk/jre/bin/java")
.arg("-version")
.output()?;
let output = &*String::from_utf8_lossy(&*child.stderr);
if let Some(version_raw) = JAVA_VERSION_REGEX.find(output) {
let mut raw = version_raw.as_str().chars();
raw.next();
raw.next_back();
return Ok(Some(raw.as_str().to_string()));
}
Ok(None)
}

View File

@@ -0,0 +1,161 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "snake_case")]
pub enum VersionType {
Release,
Snapshot,
OldAlpha,
OldBeta,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Version {
pub id: String,
#[serde(rename = "type")]
pub type_: VersionType,
pub url: String,
pub time: DateTime<Utc>,
pub release_time: DateTime<Utc>,
}
#[derive(Deserialize, Debug)]
pub struct LatestVersion {
pub release: String,
pub snapshot: String,
}
#[derive(Deserialize, Debug)]
pub struct VersionManifest {
pub latest: LatestVersion,
pub versions: Vec<Version>,
}
pub async fn fetch_version_manifest() -> Result<VersionManifest, reqwest::Error> {
reqwest::get("https://launchermeta.mojang.com/mc/game/version_manifest.json")
.await?
.json()
.await
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct AssetIndex {
pub id: String,
pub sha1: String,
pub size: u32,
pub total_size: u32,
pub url: String,
}
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum DownloadType {
Client,
ClientMappings,
Server,
ServerMappings,
WindowsServer,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Download {
pub sha1: String,
pub size: u32,
pub url: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct LibraryDownload {
pub path: String,
pub sha1: String,
pub size: u32,
pub url: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct LibraryDownloads {
pub artifact: Option<LibraryDownload>,
pub classifiers: Option<HashMap<String, LibraryDownload>>,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "snake_case")]
pub enum RuleAction {
Allow,
Disallow,
}
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum Os {
Osx,
Windows,
Linux,
Unknown,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct OsRule {
pub name: Os,
pub version: Option<String>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct LibraryRule {
pub action: RuleAction,
pub os: Option<OsRule>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct LibraryExtract {
pub exclude: Option<Vec<String>>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Library {
pub downloads: LibraryDownloads,
pub extract: Option<LibraryExtract>,
pub name: String,
pub natives: Option<HashMap<Os, String>>,
pub rules: Option<Vec<LibraryRule>>,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct VersionInfo {
pub asset_index: AssetIndex,
pub assets: String,
pub downloads: HashMap<DownloadType, Download>,
pub id: String,
pub libraries: Vec<Library>,
pub main_class: String,
pub minecraft_arguments: String,
pub minimum_launcher_version: u32,
pub release_time: DateTime<Utc>,
pub time: DateTime<Utc>,
#[serde(rename = "type")]
pub type_: VersionType,
}
pub async fn fetch_version_info(version: &Version) -> Result<VersionInfo, reqwest::Error> {
reqwest::get(&version.url).await?.json().await
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Asset {
pub hash: String,
pub size: u32,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct AssetsIndex {
pub objects: HashMap<String, Asset>,
}
pub async fn fetch_assets_index(version: &VersionInfo) -> Result<AssetsIndex, reqwest::Error> {
reqwest::get(&version.asset_index.url).await?.json().await
}

View File

@@ -0,0 +1,4 @@
mod auth;
pub mod download;
pub mod java;
pub mod meta;

11
theseus/src/lib.rs Normal file
View File

@@ -0,0 +1,11 @@
#![warn(missing_docs, unused_import_braces, missing_debug_implementations)]
pub mod launcher;
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}