1
0

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

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

8
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/theseus.iml" filepath="$PROJECT_DIR$/.idea/theseus.iml" />
</modules>
</component>
</project>

49
.idea/theseus.iml generated Normal file
View File

@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/theseus/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/theseus_cli/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/target/debug/build/memchr-f931d8711453d858/out" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/target/debug/build/proc-macro2-9fe81105fb85f6f3/out" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/target/debug/build/semver-bafb0545aca51a71/out" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/target/debug/build/syn-4186910aa7ddbfea/out" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/target/debug/build/bitflags-88b50a288ea9b589/out" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/target/debug/build/encoding_rs-aeb75e3d7246cd68/out" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/target/debug/build/futures-channel-0dafdf8e6c56074a/out" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/target/debug/build/futures-core-282be4c3d523964b/out" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/target/debug/build/futures-task-0e283041f0d04d9b/out" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/target/debug/build/futures-util-53aed327071491e2/out" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/target/debug/build/httparse-16fab0b2d90ac92a/out" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/target/debug/build/indexmap-b9724370d61f58d6/out" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/target/debug/build/libc-d1245e7816855bad/out" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/target/debug/build/log-655c062cb95acdc8/out" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/target/debug/build/native-tls-4354688a3ac4c342/out" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/target/debug/build/num-integer-243211f2ff028008/out" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/target/debug/build/num-traits-267e007703d2b65d/out" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/target/debug/build/openssl-4b1b3d11a2faa97c/out" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/target/debug/build/openssl-sys-a24d131c4a98488f/out" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/target/debug/build/ryu-3b42f9ed428b96b3/out" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/target/debug/build/serde-4a54573c5a9c851c/out" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/target/debug/build/serde-a78322380378b8a8/out" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/target/debug/build/serde_derive-24d0f79641168b3d/out" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/target/debug/build/serde_json-e22a1220ea14f789/out" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/target/debug/build/syn-3c24ac6a6f9c5ce5/out" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/target/debug/build/tokio-42e357298afc1829/out" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/target/debug/build/futures-channel-aaca776527d6f74c/out" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/target/debug/build/futures-macro-8bed64c7e4137015/out" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/target/debug/build/futures-task-99072bb77352f68a/out" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/target/debug/build/futures-util-84b73402819de30c/out" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/target/debug/build/proc-macro-hack-a3cb2722e7116198/out" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/target/debug/build/proc-macro-nested-c16c856e8deac6ce/out" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/target/debug/build/bzip2-sys-75559f3495f10aba/out" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/target/debug/build/crc32fast-f112d4af2349618a/out" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/target/debug/build/miniz_oxide-d683d4a97661215e/out" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

1298
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

6
Cargo.toml Normal file
View File

@@ -0,0 +1,6 @@
[workspace]
members = [
"theseus",
"theseus_cli"
]

24
theseus/Cargo.toml Normal file
View File

@@ -0,0 +1,24 @@
[package]
name = "theseus"
version = "0.1.0"
authors = ["Jai A <jaiagr+gpg@pm.me>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
thiserror = "1.0"
reqwest = { version = "0.11", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
chrono = { version = "0.4", features = ["serde"] }
uuid = "0.8"
bytes = "1"
zip = "0.5"
regex = "1.5"
lazy_static = "1.4"
tokio = { version = "1", features = ["full"] }
futures = "0.3"

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);
}
}

12
theseus_cli/Cargo.toml Normal file
View File

@@ -0,0 +1,12 @@
[package]
name = "theseus_cli"
version = "0.1.0"
authors = ["Jai A <jaiagr+gpg@pm.me>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
theseus = { path = "../theseus" }
tokio = { version = "1", features = ["full"] }
futures = "0.3"

28
theseus_cli/src/main.rs Normal file
View File

@@ -0,0 +1,28 @@
use futures::{executor, future};
use std::path::Path;
#[tokio::main]
async fn main() {
let mut thing1 = theseus::launcher::meta::fetch_version_manifest()
.await
.unwrap();
// future::join_all(thing1.versions.iter().map(|x| async move {
// println!("{}", x.url);
// let version = theseus::launcher::meta::fetch_version_info(x)
// .await
// .unwrap();
//
// println!("{:?}", version);
// }))
// .await;
if let Some(version) = thing1.versions.iter().find(|x| &*x.id == "1.17") {
println!("{}", version.id);
let thing = theseus::launcher::meta::fetch_version_info(&version)
.await
.unwrap();
theseus::launcher::download::download_client(&Path::new("./versions"), &thing).await;
}
}