modified: src/launcher.rs modified: src/main.rs modified: src/www/portable.html deleted: src/www/sign_in.html
388 lines
16 KiB
Rust
388 lines
16 KiB
Rust
use core::str;
|
|
use std::io::Cursor;
|
|
use base64::Engine;
|
|
use base64::prelude::BASE64_STANDARD;
|
|
use tokio::fs::File;
|
|
use tokio::process::Command;
|
|
use tokio::sync::mpsc;
|
|
use tokio::sync::mpsc::UnboundedSender;
|
|
use crate::config::{LauncherCredentials, LauncherServer};
|
|
use crate::minecraft;
|
|
use crate::minecraft::session::SignUpResponse;
|
|
use crate::minecraft::versions::Version;
|
|
use crate::{config::LauncherConfig, minecraft::versions::VersionConfig, util};
|
|
|
|
const JAVA_ARGS: [&str; 22] = ["-Xms512M",
|
|
"-XX:+UnlockExperimentalVMOptions",
|
|
"-XX:+DisableExplicitGC",
|
|
"-XX:MaxGCPauseMillis=200",
|
|
"-XX:+AlwaysPreTouch",
|
|
"-XX:+ParallelRefProcEnabled",
|
|
"-XX:+UseG1GC",
|
|
"-XX:G1NewSizePercent=30",
|
|
"-XX:G1MaxNewSizePercent=40",
|
|
"-XX:G1HeapRegionSize=8M",
|
|
"-XX:G1ReservePercent=20",
|
|
"-XX:InitiatingHeapOccupancyPercent=15",
|
|
"-XX:G1HeapWastePercent=5",
|
|
"-XX:G1MixedGCCountTarget=4",
|
|
"-XX:G1MixedGCLiveThresholdPercent=90",
|
|
"-XX:G1RSetUpdatingPauseTimePercent=5",
|
|
"-XX:+UseStringDeduplication", "-Dfile.encoding=UTF-8", "-Dfml.ignoreInvalidMinecraftCertificates=true", "-Dfml.ignorePatchDiscrepancies=true", "-Djava.net.useSystemProxies=true", "-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump"];
|
|
|
|
#[derive(Default)]
|
|
pub struct Launcher {
|
|
pub config: LauncherConfig,
|
|
}
|
|
|
|
impl Launcher {
|
|
|
|
pub fn is_portable(&self) -> bool {
|
|
crate::config::get_relative_launcher_dir().exists()
|
|
}
|
|
|
|
pub fn is_config_exist(&self) -> bool {
|
|
self.config.config_path().exists()
|
|
}
|
|
|
|
pub fn load_config(&mut self) {
|
|
if self.is_config_exist() {
|
|
self.config = toml::from_str(
|
|
str::from_utf8(&std::fs::read(self.config.config_path()).unwrap()).unwrap()).unwrap();
|
|
}
|
|
}
|
|
|
|
pub fn save_config(&self) {
|
|
let _ = std::fs::write(self.config.config_path(), toml::to_string_pretty(&self.config).unwrap());
|
|
}
|
|
|
|
pub fn init_config(&mut self, user_name: String) {
|
|
self.load_config();
|
|
self.config.set_username(user_name);
|
|
self.save_config();
|
|
}
|
|
|
|
fn save_server_info(&mut self, uuid: String, username: String, password: String, domain: String, session_server_port: u16, server_port: u16) -> (bool, &str) {
|
|
self.config.add_server(LauncherServer {
|
|
domain,
|
|
port: server_port,
|
|
session_server_port,
|
|
credentials: LauncherCredentials {
|
|
uuid,
|
|
username,
|
|
password
|
|
}
|
|
});
|
|
self.save_config();
|
|
(true, "You are successfully registered")
|
|
}
|
|
|
|
pub async fn register_user_server(&mut self, server: String, username: String, password: String) -> (bool, &str) {
|
|
let mut session_server_port: u16 = 8999;
|
|
let mut server_port: u16 = 25565;
|
|
let mut domain = server.clone();
|
|
if let Some(index) = server.find("#") {
|
|
let (a,b) = server.split_at(index+1);
|
|
session_server_port = b.parse().unwrap();
|
|
domain = a[..a.len()-1].to_string();
|
|
}
|
|
|
|
if let Some(index) = domain.find(":") {
|
|
let dmc = domain.clone();
|
|
let (a,b) = dmc.split_at(index+1);
|
|
domain = a[..a.len()-1].to_string();
|
|
server_port = b.parse().unwrap();
|
|
}
|
|
|
|
println!("Server information: {}:{} session={}", domain, server_port, session_server_port);
|
|
|
|
match minecraft::session::try_signup(domain.clone(), session_server_port, username.clone(), password.clone()).await {
|
|
Ok(status) => match status {
|
|
SignUpResponse::ServerError => (false, "Internal server error"),
|
|
SignUpResponse::BadCredentials => (false, "Username or password is not valid"),
|
|
SignUpResponse::UserAlreadyExists => (false, "User already exists"),
|
|
SignUpResponse::Registered(uuid) => self.save_server_info(uuid, username, password, domain, session_server_port, server_port)
|
|
}
|
|
Err(_e) => (false, "Internal server error")
|
|
}
|
|
}
|
|
|
|
pub async fn get_servers_list(&self) -> Vec<(String, String, Option<String>)> {
|
|
let mut v = Vec::new();
|
|
let servers = self.config.servers();
|
|
for server in servers {
|
|
v.push((server.domain.clone(), server.credentials.username.clone(), minecraft::server::get_server_icon(&server.domain, server.port).await.unwrap_or(None)));
|
|
}
|
|
v
|
|
}
|
|
|
|
pub fn get_instances_list(&self) -> Vec<(String, String, String)> {
|
|
let mut v = Vec::new();
|
|
let mut instances = self.config.launcher_dir();
|
|
instances.push("instances");
|
|
if let Ok(entries) = std::fs::read_dir(instances) {
|
|
for entry in entries {
|
|
if entry.is_err() { continue; }
|
|
let entry = entry.unwrap();
|
|
if !entry.metadata().unwrap().is_dir() { continue; }
|
|
let mut p = entry.path();
|
|
p.push("client.json");
|
|
if let Ok(data) = std::fs::read(p) {
|
|
let config: VersionConfig = serde_json::from_slice(&data).unwrap();
|
|
let c_type = config.r#type;
|
|
let c_type = c_type.as_str();
|
|
v.push((config.id, c_type.to_string(), format!("data:image/png;base64,{}", BASE64_STANDARD.encode(match c_type {
|
|
"old_alpha" => include_bytes!("www/icons/alpha.png").to_vec(),
|
|
"old_beta" => include_bytes!("www/icons/beta.png").to_vec(),
|
|
"release" | "snapshot" => include_bytes!("www/icons/release.png").to_vec(),
|
|
_ => include_bytes!("www/icons/glowstone.png").to_vec()
|
|
}))));
|
|
}
|
|
}
|
|
}
|
|
v
|
|
}
|
|
|
|
pub fn get_screenshots(&self) -> Vec<(String, String)> {
|
|
let mut v = Vec::new();
|
|
let mut instances = self.config.launcher_dir();
|
|
instances.push("instances");
|
|
if let Ok(entries) = std::fs::read_dir(instances) {
|
|
for entry in entries {
|
|
if entry.is_err() { continue; }
|
|
let entry = entry.unwrap();
|
|
if !entry.metadata().unwrap().is_dir() { continue; }
|
|
let mut p = entry.path();
|
|
p.push("data");
|
|
p.push("screenshots");
|
|
if !p.exists() { continue; }
|
|
if let Ok(screenshots) = std::fs::read_dir(p) {
|
|
for screenshot in screenshots {
|
|
if let Ok(screenshot) = screenshot {
|
|
if screenshot.file_name().to_str().unwrap().ends_with("png") {
|
|
v.push((screenshot.path().to_str().unwrap().to_string(), format!("data:image/png;base64,{}", BASE64_STANDARD.encode(std::fs::read(screenshot.path()).unwrap()))));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
v
|
|
}
|
|
|
|
pub async fn launch_instance(&self, instance_name: String, username: String, uuid: String, token: String) {
|
|
let mut instances = self.config.launcher_dir();
|
|
instances.push("instances");
|
|
instances.push(&instance_name);
|
|
instances.push("client.json");
|
|
|
|
let mut client_jar = self.config.launcher_dir();
|
|
client_jar.push("instances");
|
|
client_jar.push(&instance_name);
|
|
client_jar.push("client.jar");
|
|
|
|
let mut instance_dir = self.config.launcher_dir();
|
|
instance_dir.push("instances");
|
|
instance_dir.push(&instance_name);
|
|
instance_dir.push("data");
|
|
|
|
let mut cmd = Command::new(&self.config.java_path);
|
|
cmd.current_dir(instance_dir);
|
|
cmd.stdout(std::process::Stdio::piped());
|
|
|
|
|
|
for arg in JAVA_ARGS {
|
|
cmd.arg(arg.to_string());
|
|
}
|
|
|
|
cmd.arg(["-Xmx", &self.config.ram_amount.to_string(), "M"].concat());
|
|
|
|
let mut natives_path = self.config.launcher_dir();
|
|
natives_path.push("instances");
|
|
natives_path.push(&instance_name);
|
|
natives_path.push("natives");
|
|
|
|
cmd.arg(["-Djava.library.path=", natives_path.to_str().unwrap() ].concat());
|
|
cmd.arg(["-Dminecraft.client.jar=", client_jar.to_str().unwrap()].concat());
|
|
cmd.arg("-cp".to_string());
|
|
|
|
if let Ok(data) = std::fs::read(&instances) {
|
|
let config: VersionConfig = serde_json::from_slice(&data).unwrap();
|
|
let mut libraries_cmd = Vec::new();
|
|
for library in config.libraries {
|
|
if let Some(classifier) = &library.downloads.classifiers {
|
|
if let Some(natives) = &classifier.natives {
|
|
let rel_path = &natives.path;
|
|
let mut libs = self.config.launcher_dir();
|
|
libs.push("libraries");
|
|
let rel_path = [libs.to_str().unwrap(), "\\", &rel_path.replace("/", "\\")].concat();
|
|
let data = std::fs::read(rel_path).unwrap();
|
|
|
|
zip_extract::extract(Cursor::new(data), &natives_path, true);
|
|
}
|
|
} else {
|
|
let mut libs = self.config.launcher_dir();
|
|
libs.push("libraries");
|
|
libs.push(library.to_pathbuf_file());
|
|
libraries_cmd.push([libs.to_str().unwrap(), ";"].concat());
|
|
}
|
|
}
|
|
libraries_cmd.push(client_jar.to_str().unwrap().to_string());
|
|
cmd.arg(libraries_cmd.concat());
|
|
cmd.arg(config.mainClass.clone());
|
|
|
|
let mut game_dir = self.config.launcher_dir();
|
|
game_dir.push("instances");
|
|
game_dir.push(&instance_name);
|
|
game_dir.push("data");
|
|
|
|
let mut assets_dir = self.config.launcher_dir();
|
|
assets_dir.push("assets");
|
|
cmd.args(&["--username", &username, "--version", &instance_name, "--gameDir", game_dir.to_str().unwrap(), "--assetsDir", assets_dir.to_str().unwrap(), "--assetIndex", &config.assetIndex.id, "--uuid", &uuid, "--accessToken", &token, "--userProperties", "{}", "--userType", "mojang", "--width", "925", "--height", "530"]);
|
|
let child = cmd.spawn().unwrap();
|
|
|
|
/*if let Some(stdout) = child.stdout.take() {
|
|
let reader = BufReader::new(stdout);
|
|
let mut lines = reader.lines();
|
|
|
|
while let Ok(Some(line)) = lines.next_line().await {
|
|
println!("Line: {}", line);
|
|
}
|
|
}*/
|
|
}
|
|
}
|
|
|
|
|
|
pub async fn new_vanilla_instance(&mut self, config: VersionConfig, version_object: &Version, sender: UnboundedSender<(u8, String)>) {
|
|
|
|
let (sx, mut rx) = mpsc::unbounded_channel();
|
|
|
|
let root = self.config.launcher_dir();
|
|
let mut instances = root.clone();
|
|
instances.push("instances");
|
|
instances.push(&config.id);
|
|
|
|
let _ = std::fs::create_dir_all(&instances);
|
|
|
|
instances.push("client.jar");
|
|
|
|
let mut overall_size = config.downloads.client.size as usize;
|
|
let mut cnt = 0;
|
|
|
|
let client_jar_url = config.downloads.client.url;
|
|
|
|
let mut client_json_path = root.clone();
|
|
client_json_path.push("instances");
|
|
client_json_path.push(config.id);
|
|
client_json_path.push("client.json");
|
|
|
|
let _ = util::download_file(&version_object.url, client_json_path.to_str().unwrap(), sx.clone(), "Downloading client.json");
|
|
cnt += 1;
|
|
|
|
let _ = util::download_file(&client_jar_url, instances.to_str().unwrap(), sx.clone(), "Downloading client.jar");
|
|
cnt += 1;
|
|
|
|
let mut libraries = root.clone();
|
|
libraries.push("libraries");
|
|
|
|
for i in 0..config.libraries.len() {
|
|
let library = &config.libraries[i];
|
|
if let Some(artifact) = &library.downloads.artifact {
|
|
let mut dl_path = libraries.clone();
|
|
let mut dl_pp = libraries.clone();
|
|
dl_pp.push(library.to_pathbuf_path());
|
|
let _ = std::fs::create_dir_all(dl_pp);
|
|
dl_path.push(library.to_pathbuf_file());
|
|
if File::open(dl_path.to_str().unwrap()).await.is_err() {
|
|
overall_size += artifact.size as usize;
|
|
let _ = util::download_file(&artifact.url, dl_path.to_str().unwrap(), sx.clone(), "Downloading libraries");
|
|
cnt += 1;
|
|
}
|
|
}
|
|
|
|
if let Some(classifiers) = &library.downloads.classifiers {
|
|
if let Some(natives) = &classifiers.natives {
|
|
let mut dl_path = libraries.clone();
|
|
dl_path.push(&natives.path);
|
|
let t_p = dl_path.to_str().unwrap().split("/").collect::<Vec<&str>>();
|
|
let t_p = t_p[..t_p.len()-1].join("/");
|
|
let _ = std::fs::create_dir_all(&t_p);
|
|
if File::open(dl_path.to_str().unwrap()).await.is_err() {
|
|
overall_size += natives.size as usize;
|
|
let _ = util::download_file(&natives.url, dl_path.to_str().unwrap(), sx.clone(), "Downloading natives");
|
|
cnt += 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
let mut assets_path = root.clone();
|
|
assets_path.push("assets");
|
|
|
|
let mut indexes = assets_path.clone();
|
|
indexes.push("indexes");
|
|
let _ = std::fs::create_dir_all(indexes);
|
|
|
|
let mut objects = assets_path.clone();
|
|
objects.push("objects");
|
|
let _ = std::fs::create_dir_all(objects);
|
|
|
|
let mut index = assets_path.clone();
|
|
index.push(config.assetIndex.to_path());
|
|
|
|
let _ = util::download_file(&config.assetIndex.url, index.to_str().unwrap(), sx.clone(), "Downloading assets indexes");
|
|
cnt += 1;
|
|
|
|
let asset_index = config.assetIndex.url;
|
|
|
|
overall_size += config.assetIndex.size as usize;
|
|
overall_size += config.assetIndex.totalSize as usize;
|
|
|
|
let assets = crate::minecraft::assets::fetch_assets_list(&asset_index).await.unwrap().objects;
|
|
|
|
for (_key, asset) in assets {
|
|
let mut single_object = assets_path.clone();
|
|
single_object.push(asset.to_path());
|
|
|
|
let mut single_object_path = assets_path.clone();
|
|
single_object_path.push(asset.to_small_path());
|
|
std::fs::create_dir_all(single_object_path);
|
|
|
|
if File::open(single_object.to_str().unwrap()).await.is_err() {
|
|
util::download_file(&asset.to_url(), single_object.to_str().unwrap(), sx.clone(), "Downloading assets objects");
|
|
cnt += 1;
|
|
}
|
|
}
|
|
|
|
tokio::spawn(async move {
|
|
let mut current_size = 0;
|
|
let mut current_cnt = 0;
|
|
while let Some((size, status)) = rx.recv().await {
|
|
current_size += size;
|
|
current_cnt += 1;
|
|
sender.send((((current_size as f32 / overall_size as f32) * 100.0) as u8, status));
|
|
if current_cnt >= cnt {
|
|
sender.send((100, "_".to_string()));
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
pub fn init_dirs(&self) {
|
|
let root = self.config.launcher_dir();
|
|
std::fs::create_dir_all(&root);
|
|
// instances assets libraries config.toml
|
|
let mut instances = root.clone();
|
|
instances.push("instances");
|
|
|
|
let mut assets = root.clone();
|
|
assets.push("assets");
|
|
|
|
let mut libraries = root.clone();
|
|
libraries.push("libraries");
|
|
|
|
std::fs::create_dir_all(&instances);
|
|
std::fs::create_dir_all(&assets);
|
|
std::fs::create_dir_all(&libraries);
|
|
}
|
|
} |