diff --git a/Cargo.lock b/Cargo.lock index 70c20d5..bb7ff78 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -812,16 +812,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "nu-ansi-term" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" -dependencies = [ - "overload", - "winapi", -] - [[package]] name = "num-traits" version = "0.2.16" @@ -912,12 +902,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - [[package]] name = "parking_lot" version = "0.12.1" @@ -1214,15 +1198,6 @@ dependencies = [ "digest", ] -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -1286,7 +1261,7 @@ dependencies = [ [[package]] name = "syntax_bootstrapper" -version = "1.2.1" +version = "1.2.0" dependencies = [ "chrono", "colored", @@ -1298,8 +1273,6 @@ dependencies = [ "reqwest", "term_size", "tokio", - "tracing", - "tracing-subscriber", "winreg 0.51.0", "winres", "zip-extract", @@ -1348,16 +1321,6 @@ dependencies = [ "syn", ] -[[package]] -name = "thread_local" -version = "1.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" -dependencies = [ - "cfg-if", - "once_cell", -] - [[package]] name = "time" version = "0.1.45" @@ -1472,59 +1435,22 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ + "cfg-if", "pin-project-lite", - "tracing-attributes", "tracing-core", ] -[[package]] -name = "tracing-attributes" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" dependencies = [ "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" -dependencies = [ - "nu-ansi-term", - "sharded-slab", - "smallvec", - "thread_local", - "tracing-core", - "tracing-log", ] [[package]] @@ -1577,12 +1503,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "valuable" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" - [[package]] name = "vcpkg" version = "0.2.15" diff --git a/Cargo.toml b/Cargo.toml index 1c2728f..525585c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,8 +17,6 @@ tokio = { version = "1.32.0", features=["full"]} futures-util = "0.3.28" md5 = "0.7.0" zip-extract = "0.1.2" -tracing = "0.1.40" -tracing-subscriber = "0.3.17" [target.'cfg(windows)'.dependencies] winreg = "0.51.0" diff --git a/src/ascii.txt b/src/ascii.txt deleted file mode 100644 index 577be38..0000000 --- a/src/ascii.txt +++ /dev/null @@ -1,8 +0,0 @@ - .d8888b. Y88b d88P 888b 888 88888888888 d8888 Y88b d88P - d88P Y88b Y88b d88P 8888b 888 888 d88888 Y88b d88P - Y88b. Y88o88P 88888b 888 888 d88P888 Y88o88P - \"Y888b. Y888P 888Y88b 888 888 d88P 888 Y888P - \"Y88b. 888 888 Y88b888 888 d88P 888 d888b - \"888 888 888 Y88888 888 d88P 888 d88888b - Y88b d88P 888 888 Y8888 888 d8888888888 d88P Y88b - \"Y8888P\" 888 888 Y888 888 d88P 888 d88P Y88b \ No newline at end of file diff --git a/src/constants/mod.rs b/src/constants/mod.rs deleted file mode 100644 index d5e1e23..0000000 --- a/src/constants/mod.rs +++ /dev/null @@ -1,19 +0,0 @@ -pub const FILES_TO_DOWNLOAD: [[&str; 2]; 17] = [ - ["SyntaxApp.zip", "./"], - ["NPSyntaxProxy.zip", "./"], - ["SyntaxProxy.zip", "./"], - ["Libraries.zip", "./"], - ["redist.zip", "./"], - ["content-textures.zip", "./content/textures"], - ["content-textures2.zip", "./content/textures"], - ["content-textures3.zip", "./content/textures"], - ["content-terrain.zip", "./content/terrain"], - ["content-fonts.zip", "./content/fonts"], - ["content-sounds.zip", "./content/sounds"], - ["content-scripts.zip", "./content/scripts"], - ["content-sky.zip", "./content/sky"], - ["content-music.zip", "./content/music"], - ["content-particles.zip", "./content/particles"], - ["2018client.zip", "./Client2018"], - ["2020client.zip", "./Client2020"], -]; diff --git a/src/main.rs b/src/main.rs index 8151ccd..bf6a159 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,83 +1,159 @@ use colored::*; +use std::path::PathBuf; +use reqwest::Client; use dirs::data_local_dir; use futures_util::StreamExt; use md5; -use metadata::LevelFilter; -use reqwest::Client; -use std::path::PathBuf; -use tokio::task::JoinSet; use zip_extract; -mod constants; -mod util; - -use constants::*; - -use tracing::*; -use util::*; - -#[cfg(not(target_os = "windows"))] -use std::io::prelude::*; -#[cfg(not(target_os = "windows"))] -use std::os::unix::fs::FileExt; #[cfg(target_os = "windows")] use std::os::windows::prelude::FileExt; #[cfg(target_os = "windows")] use winreg::enums::*; #[cfg(target_os = "windows")] use winreg::RegKey; +#[cfg(not(target_os = "windows"))] +use std::io::prelude::*; +#[cfg(not(target_os = "windows"))] +use std::os::unix::fs::FileExt; + +fn info( message : &str ) { + let time = chrono::Local::now().format("%H:%M:%S").to_string(); + println!("[{}] [{}] {}", time.bold().blue(), "INFO".bold().green(), message); +} + +fn error( message : &str ) { + let time = chrono::Local::now().format("%H:%M:%S").to_string(); + println!("[{}] [{}] {}", time.bold().blue(), "ERROR".bold().red(), message); +} #[cfg(debug_assertions)] -const DEBUG: bool = true; -#[cfg(debug_assertions)] -const MAX_TRACING_LEVEL: LevelFilter = LevelFilter::DEBUG; -#[cfg(not(debug_assertions))] -const DEBUG: bool = false; -#[cfg(not(debug_assertions))] -const MAX_TRACING_LEVEL: LevelFilter = LevelFilter::ERROR; +fn debug( message : &str ) { + let time = chrono::Local::now().format("%H:%M:%S").to_string(); + println!("[{}] [{}] {}", time.bold().blue(), "DEBUG".bold().yellow(), message); +} -const ASCII_ART: &str = include_str!("./ascii.txt"); +#[cfg(not(debug_assertions))] +fn debug( message : &str ) {} + +pub async fn http_get( client: &Client ,url: &str ) -> Result { + debug(&format!("{} {}", "GET".green(), url.bright_blue())); + let response = client.get(url).send().await; + if (response.is_err()) { + debug(&format!("Failed to fetch {}", url.bright_blue())); + return Err(response.err().unwrap()); + } + let response_body = response.unwrap().text().await.unwrap(); + Ok(response_body) +} + +pub async fn download_file( client: &Client, url: &str, path: &PathBuf ) { + debug(&format!("{} {}", "GET".green(), url.bright_blue())); + let response = client.get(url).send().await.unwrap(); + let content_length = response.content_length().unwrap(); + debug(&format!("Content Length: {}", content_length)); + + let time = chrono::Local::now().format("%H:%M:%S").to_string(); + let pg_bar_str = " {spinner:.green} [{bar:40.cyan/blue}] {bytes}/{total_bytes} ({eta})"; + let progress_bar = indicatif::ProgressBar::new(content_length); + let progress_style = indicatif::ProgressStyle::default_bar() + .template( + format!("{}\n{}", + format!( + "[{}] [{}] {}", + time.bold().blue(), + "INFO".bold().green(), + &format!("Downloading {}", &url.bright_blue()) + ), + pg_bar_str).as_str() + ) + .unwrap().progress_chars("#>-"); + progress_bar.set_style(progress_style); + progress_bar.set_message("Downloading File"); + + let file = std::fs::File::create(path).unwrap(); + let mut downloaded: u64 = 0; + let mut stream = response.bytes_stream(); + + while let Some(item) = stream.next().await { + let chunk = item.or(Err(format!("Error while downloading file"))).unwrap(); + #[cfg(target_os = "windows")] + { + file.seek_write(chunk.as_ref(), downloaded).unwrap(); + } + #[cfg(not(target_os = "windows"))] + { + file.write_at(chunk.as_ref(), downloaded).unwrap(); + } + let new = std::cmp::min(downloaded + (chunk.len() as u64), content_length); + downloaded = new; + progress_bar.set_position(new); + } + progress_bar.finish(); + info(format!("Finished downloading {}", url.green()).as_str()); +} + +pub async fn download_file_prefix( client: &Client, url: &str, path_prefix : &PathBuf ) -> PathBuf { + let path = path_prefix.join(generate_md5(url).await); + download_file(client, url, &path).await; + return path; +} + +pub async fn generate_md5( input : &str ) -> String { + let hashed_input = md5::compute(input.as_bytes()); + return format!("{:x}", hashed_input); +} + +pub async fn create_folder_if_not_exists( path: &PathBuf ) { + if !path.exists() { + info(&format!("Creating folder {}", path.to_str().unwrap().bright_blue())); + std::fs::create_dir_all(path).unwrap(); + } +} + +fn get_installation_directory() -> PathBuf { + return PathBuf::from(data_local_dir().unwrap().to_str().unwrap()).join("Syntax"); +} #[tokio::main] async fn main() { + // Clear the terminal before printing the startup text #[cfg(target_os = "windows")] { std::process::Command::new("cmd") - .args(&["/c", "cls"]) - .spawn() - .expect("cls command failed to start") - .wait() - .expect("failed to wait"); + .args(&["/c", "cls"]) + .spawn() + .expect("cls command failed to start") + .wait() + .expect("failed to wait"); } #[cfg(not(target_os = "windows"))] { std::process::Command::new("clear").spawn().unwrap(); } - tracing_subscriber::fmt() - .with_max_level(MAX_TRACING_LEVEL) - .pretty() - .init(); - let args: Vec = std::env::args().collect(); - let base_url: &str = "www.syntax.eco"; - let mut setup_url: &str = "setup.syntax.eco"; - let fallback_setup_url: &str = "d2f3pa9j0u8v6f.cloudfront.net"; - let mut bootstrapper_filename: &str = "SyntaxPlayerLauncher.exe"; + let base_url : &str = "www.syntax.eco"; + let mut setup_url : &str = "setup.syntax.eco"; + let fallback_setup_url : &str = "d2f3pa9j0u8v6f.cloudfront.net"; + let mut bootstrapper_filename :&str = "SyntaxPlayerLauncher.exe"; #[cfg(not(target_os = "windows"))] { bootstrapper_filename = "SyntaxPlayerLinuxLauncher"; } let build_date = include_str!(concat!(env!("OUT_DIR"), "/build_date.txt")); - let startup_text = ASCII_ART.to_owned(); - - let bootstrapper_info = format!( - "{} | Build Date: {} | Version: {}", - base_url, - build_date, - env!("CARGO_PKG_VERSION"), - ); + let startup_text = format!(" + .d8888b. Y88b d88P 888b 888 88888888888 d8888 Y88b d88P + d88P Y88b Y88b d88P 8888b 888 888 d88888 Y88b d88P + Y88b. Y88o88P 88888b 888 888 d88P888 Y88o88P + \"Y888b. Y888P 888Y88b 888 888 d88P 888 Y888P + \"Y88b. 888 888 Y88b888 888 d88P 888 d888b + \"888 888 888 Y88888 888 d88P 888 d88888b + Y88b d88P 888 888 Y8888 888 d8888888888 d88P Y88b + \"Y8888P\" 888 888 Y888 888 d88P 888 d88P Y88b + + {} | Build Date: {} | Version: {}", base_url ,build_date, env!("CARGO_PKG_VERSION")); // Format the startup text to be centered let mut terminal_width = 80; @@ -85,80 +161,51 @@ async fn main() { terminal_width = w; } if terminal_width < 80 { - print!( - "{}\n", - format!( - "SYNTAX Bootstrapper | {} | Build Date: {} | Version: {}", - base_url, - build_date, - env!("CARGO_PKG_VERSION") - ) - .to_string() - .magenta() - .cyan() - .italic() - .on_black() - ); // Fallback message + print!("{}\n", format!("SYNTAX Bootstrapper | {} | Build Date: {} | Version: {}", base_url, build_date, env!("CARGO_PKG_VERSION")).to_string().magenta().cyan().italic().on_black()); // Fallback message } else { let startup_text_lines = startup_text.lines().collect::>(); //println!("{}", startup_text.bold().blue().on_black()); - + // print all lines except the last one - for line in startup_text_lines { + for line in &startup_text_lines[0..startup_text_lines.len() - 1] { let spaces = (terminal_width - line.len()) / 2; let formatted_line = format!("{}{}", " ".repeat(spaces), line); println!("{}", formatted_line.bright_magenta().italic().on_black()); } // print last line as a different color - println!( - "{}\n", - bootstrapper_info.magenta().cyan().italic().on_black() - ); + let last_line = startup_text_lines[startup_text_lines.len() - 1]; + let spaces = (terminal_width - last_line.len()) / 2; + let last_line = format!("{}{}", " ".repeat(spaces), last_line); + println!("{}\n", last_line.magenta().cyan().italic().on_black()); } - let http_client: Client = reqwest::Client::builder().no_gzip().build().unwrap(); - debug!( - "Setup Server: {} | Base Server: {}", - setup_url.bright_blue(), - base_url.bright_blue() - ); - - debug!("Fetching latest client version from setup server"); - - let latest_client_version: String; - let latest_client_version_response = - http_get(&http_client, &format!("https://{}/version", setup_url)).await; + let http_client: Client = reqwest::Client::builder() + .no_gzip() + .build() + .unwrap(); + debug(format!("Setup Server: {} | Base Server: {}", setup_url.bright_blue(), base_url.bright_blue()).as_str()); + debug("Fetching latest client version from setup server"); + + let latest_client_version : String; + let latest_client_version_response = http_get(&http_client ,&format!("https://{}/version", setup_url)).await; match latest_client_version_response { Ok(latest_client_version_result) => { - debug!( - "Latest Client Version: {}", - latest_client_version_result.bright_blue() - ); + debug(&format!("Latest Client Version: {}", latest_client_version_result.bright_blue())); latest_client_version = latest_client_version_result; - } + }, Err(e) => { - error!("Failed to fetch latest client version from setup server: [{}], attempting to fallback to {}", e.to_string().bright_red(), fallback_setup_url.bright_blue()); - let fallback_client_version_response = http_get( - &http_client, - &format!("https://{}/version", fallback_setup_url), - ) - .await; + error(&format!("Failed to fetch latest client version from setup server: [{}], attempting to fallback to {}", e.to_string().bright_red(), fallback_setup_url.bright_blue())); + let fallback_client_version_response = http_get(&http_client ,&format!("https://{}/version", fallback_setup_url)).await; match fallback_client_version_response { Ok(fallback_client_version_result) => { - info!( - "Successfully fetched latest client version from fallback setup server: {}", - fallback_setup_url.bright_blue() - ); - debug!( - "Latest Client Version: {}", - fallback_client_version_result.bright_blue() - ); + info(&format!("Successfully fetched latest client version from fallback setup server: {}", fallback_setup_url.bright_blue())); + debug(&format!("Latest Client Version: {}", fallback_client_version_result.bright_blue())); latest_client_version = fallback_client_version_result; setup_url = fallback_setup_url; - } + }, Err(e) => { - error!("Failed to fetch latest client version from fallback setup server: {}, are you connected to the internet?", e); + error(&format!("Failed to fetch latest client version from fallback setup server: {}, are you connected to the internet?", e)); std::thread::sleep(std::time::Duration::from_secs(10)); std::process::exit(0); } @@ -167,58 +214,35 @@ async fn main() { } // Wait for the latest client version to be fetched - info!( - "Latest Client Version: {}", - latest_client_version.cyan().underline() - ); - debug!("Setup Server: {}", setup_url.cyan().underline()); + info(&format!("Latest Client Version: {}", latest_client_version.cyan().underline())); + debug(&format!("Setup Server: {}", setup_url.cyan().underline())); let installation_directory = get_installation_directory(); - debug!( - "Installation Directory: {}", - installation_directory.to_str().unwrap().bright_blue() - ); + debug(&format!("Installation Directory: {}", installation_directory.to_str().unwrap().bright_blue())); create_folder_if_not_exists(&installation_directory).await; let versions_directory = installation_directory.join("Versions"); - debug!( - "Versions Directory: {}", - versions_directory.to_str().unwrap().bright_blue() - ); + debug(&format!("Versions Directory: {}", versions_directory.to_str().unwrap().bright_blue())); create_folder_if_not_exists(&versions_directory).await; let temp_downloads_directory = installation_directory.join("Downloads"); - debug!( - "Temp Downloads Directory: {}", - temp_downloads_directory.to_str().unwrap().bright_blue() - ); + debug(&format!("Temp Downloads Directory: {}", temp_downloads_directory.to_str().unwrap().bright_blue())); create_folder_if_not_exists(&temp_downloads_directory).await; let current_version_directory = versions_directory.join(format!("{}", latest_client_version)); - debug!( - "Current Version Directory: {}", - current_version_directory.to_str().unwrap().bright_blue() - ); + debug(&format!("Current Version Directory: {}", current_version_directory.to_str().unwrap().bright_blue())); create_folder_if_not_exists(¤t_version_directory).await; let latest_bootstrapper_path = current_version_directory.join(bootstrapper_filename); // Is the program currently running from the latest version directory? let current_exe_path = std::env::current_exe().unwrap(); // If the current exe path is not in the current version directory, then we need to run the latest bootstrapper ( download if needed ) - if !current_exe_path.starts_with(¤t_version_directory) && !DEBUG { + if !current_exe_path.starts_with(¤t_version_directory) { // Check if the latest bootstrapper is downloaded if !latest_bootstrapper_path.exists() { - info!("Downloading the latest bootstrapper and restarting"); + info("Downloading the latest bootstrapper and restarting"); // Download the latest bootstrapper - download_to_file( - &http_client, - &format!( - "https://{}/{}-{}", - setup_url, latest_client_version, bootstrapper_filename - ), - &latest_bootstrapper_path, - ) - .await; + download_file(&http_client, &format!("https://{}/{}-{}", setup_url, latest_client_version, bootstrapper_filename), &latest_bootstrapper_path).await; } // Run the latest bootstrapper ( with the same arguments passed to us ) and exit #[cfg(target_os = "windows")] @@ -226,20 +250,12 @@ async fn main() { let mut command = std::process::Command::new(latest_bootstrapper_path.clone()); command.args(&args[1..]); match command.spawn() { - Ok(_) => {} + Ok(_) => {}, Err(e) => { - debug!(&format!("Bootstrapper errored with error {}", e)); + debug(&format!("Bootstrapper errored with error {}", e)); info("Found bootstrapper was corrupted! Downloading..."); std::fs::remove_file(latest_bootstrapper_path.clone()).unwrap(); - download_file( - &http_client, - &format!( - "https://{}/{}-{}", - setup_url, latest_client_version, bootstrapper_filename - ), - &latest_bootstrapper_path, - ) - .await; + download_file(&http_client, &format!("https://{}/{}-{}", setup_url, latest_client_version, bootstrapper_filename), &latest_bootstrapper_path).await; command.spawn().expect("Bootstrapper is still corrupted."); std::thread::sleep(std::time::Duration::from_secs(20)); } @@ -248,13 +264,9 @@ async fn main() { #[cfg(not(target_os = "windows"))] { // Make sure the latest bootstrapper is executable - std::process::Command::new("chmod") - .arg("+x") - .arg(latest_bootstrapper_path.to_str().unwrap()) - .spawn() - .unwrap(); + std::process::Command::new("chmod").arg("+x").arg(latest_bootstrapper_path.to_str().unwrap()).spawn().unwrap(); - info!("We need permission to run the latest bootstrapper"); + info("We need permission to run the latest bootstrapper"); let mut command = std::process::Command::new(latest_bootstrapper_path); command.args(&args[1..]); command.spawn().unwrap(); @@ -263,13 +275,13 @@ async fn main() { } // Looks like we are running from the latest version directory, so we can continue with the update process - // Check for "AppSettings.xml" in the current version directory + // Check for "AppSettings.xml" in the current version directory // If it doesent exist, then we got either a fresh directory or a corrupted installation // So delete the every file in the current version directory except for the Bootstrapper itself let app_settings_path = current_version_directory.join("AppSettings.xml"); let client_executable_path = current_version_directory.join("SyntaxPlayerBeta.exe"); if !app_settings_path.exists() || !client_executable_path.exists() { - info!("Downloading the latest client files, this may take a while."); + info("Downloading the latest client files, this may take a while."); for entry in std::fs::read_dir(¤t_version_directory).unwrap() { let entry = entry.unwrap(); let path = entry.path(); @@ -282,70 +294,117 @@ async fn main() { } } - let version_url_prefix = format!("https://{}/{}-", setup_url, latest_client_version); + let VersionURLPrefix = format!("https://{}/{}-", setup_url, latest_client_version); + let SyntaxAppZip : PathBuf = download_file_prefix(&http_client, format!("{}SyntaxApp.zip", VersionURLPrefix).as_str(), &temp_downloads_directory).await; + let NPSyntaxProxyZip : PathBuf = download_file_prefix(&http_client, format!("{}NPSyntaxProxy.zip", VersionURLPrefix).as_str(), &temp_downloads_directory).await; + let SyntaxProxyZip : PathBuf = download_file_prefix(&http_client, format!("{}SyntaxProxy.zip", VersionURLPrefix).as_str(), &temp_downloads_directory).await; + let LibrariesZip : PathBuf = download_file_prefix(&http_client, format!("{}Libraries.zip", VersionURLPrefix).as_str(), &temp_downloads_directory).await; + let RedistZip : PathBuf = download_file_prefix(&http_client, format!("{}redist.zip", VersionURLPrefix).as_str(), &temp_downloads_directory).await; - /* Use a joisnet to run multiple async functions at once */ + let ContentTexturesZip : PathBuf = download_file_prefix(&http_client, format!("{}content-textures.zip", VersionURLPrefix).as_str(), &temp_downloads_directory).await; + let ContentTextures2Zip : PathBuf = download_file_prefix(&http_client, format!("{}content-textures2.zip", VersionURLPrefix).as_str(), &temp_downloads_directory).await; + let ContentTextures3Zip : PathBuf = download_file_prefix(&http_client, format!("{}content-textures3.zip", VersionURLPrefix).as_str(), &temp_downloads_directory).await; + let ContentTerrainZip : PathBuf = download_file_prefix(&http_client, format!("{}content-terrain.zip", VersionURLPrefix).as_str(), &temp_downloads_directory).await; + let ContentFontsZip : PathBuf = download_file_prefix(&http_client, format!("{}content-fonts.zip", VersionURLPrefix).as_str(), &temp_downloads_directory).await; + let ContentSoundsZip : PathBuf = download_file_prefix(&http_client, format!("{}content-sounds.zip", VersionURLPrefix).as_str(), &temp_downloads_directory).await; + let ContentScriptsZip : PathBuf = download_file_prefix(&http_client, format!("{}content-scripts.zip", VersionURLPrefix).as_str(), &temp_downloads_directory).await; + let ContentSkyZip : PathBuf = download_file_prefix(&http_client, format!("{}content-sky.zip", VersionURLPrefix).as_str(), &temp_downloads_directory).await; + let ContentMusicZip : PathBuf = download_file_prefix(&http_client, format!("{}content-music.zip", VersionURLPrefix).as_str(), &temp_downloads_directory).await; + let ContentParticles : PathBuf = download_file_prefix(&http_client, format!("{}content-particles.zip", VersionURLPrefix).as_str(), &temp_downloads_directory).await; - let mut set = JoinSet::new(); + let ShadersZip : PathBuf = download_file_prefix(&http_client, format!("{}shaders.zip", VersionURLPrefix).as_str(), &temp_downloads_directory).await; - for [value, _] in FILES_TO_DOWNLOAD { - set.spawn(download_and_extract( - value.to_string(), - version_url_prefix.clone(), - current_version_directory.clone(), - )); + let Client2018Zip : PathBuf = download_file_prefix(&http_client, format!("{}2018client.zip", VersionURLPrefix).as_str(), &temp_downloads_directory).await; + let Client2020Zip : PathBuf = download_file_prefix(&http_client, format!("{}2020client.zip", VersionURLPrefix).as_str(), &temp_downloads_directory).await; + info("Download finished, extracting files."); + + fn extract_to_dir( zip_file : &PathBuf, target_dir : &PathBuf ) { + let zip_file_cursor = std::fs::File::open(zip_file).unwrap(); + zip_extract::extract(zip_file_cursor, target_dir, false).unwrap(); } + extract_to_dir(&SyntaxAppZip, ¤t_version_directory); + extract_to_dir(&NPSyntaxProxyZip, ¤t_version_directory); + extract_to_dir(&SyntaxProxyZip, ¤t_version_directory); + extract_to_dir(&LibrariesZip, ¤t_version_directory); + extract_to_dir(&RedistZip, ¤t_version_directory); - while let Some(value) = set.join_next().await { - value.unwrap() - } + let content_directory = current_version_directory.join("content"); + let platform_content_directory = current_version_directory.join("PlatformContent"); + let shaders_directory = current_version_directory.join("shaders"); - info!("Binary installed"); + create_folder_if_not_exists(&content_directory).await; + create_folder_if_not_exists(&platform_content_directory).await; + create_folder_if_not_exists(&shaders_directory).await; - /* Convert to async due to this being a slow function */ + let fonts_directory = content_directory.join("fonts"); + let music_directory = content_directory.join("music"); + let particles_directory = content_directory.join("particles"); + let sky_directory = content_directory.join("sky"); + let sounds_directory = content_directory.join("sounds"); + let textures_directory = content_directory.join("textures"); + let scripts_directory = content_directory.join("scripts"); - // Redacted for lagging vscode + create_folder_if_not_exists(&fonts_directory).await; + create_folder_if_not_exists(&music_directory).await; + create_folder_if_not_exists(&particles_directory).await; + create_folder_if_not_exists(&sky_directory).await; + create_folder_if_not_exists(&sounds_directory).await; + create_folder_if_not_exists(&textures_directory).await; + + extract_to_dir(&ContentTexturesZip, &textures_directory); + extract_to_dir(&ContentTextures2Zip, &textures_directory); + extract_to_dir(&ContentFontsZip, &fonts_directory); + extract_to_dir(&ContentSoundsZip, &sounds_directory); + extract_to_dir(&ContentSkyZip, &sky_directory); + extract_to_dir(&ContentMusicZip, &music_directory); + extract_to_dir(&ContentParticles, &particles_directory); + extract_to_dir(&ContentScriptsZip, &scripts_directory); + + let platform_pc_directory = platform_content_directory.join("pc"); + create_folder_if_not_exists(&platform_pc_directory).await; + let terrain_directory = platform_pc_directory.join("terrain"); + let textures_directory = platform_pc_directory.join("textures"); + create_folder_if_not_exists(&terrain_directory).await; + create_folder_if_not_exists(&textures_directory).await; + + extract_to_dir(&ContentTerrainZip, &terrain_directory); + extract_to_dir(&ContentTextures3Zip, &textures_directory); + extract_to_dir(&ShadersZip, &shaders_directory); + + let client_2018_directory = current_version_directory.join("Client2018"); + create_folder_if_not_exists(&client_2018_directory).await; + extract_to_dir(&Client2018Zip, &client_2018_directory); + + let client_2020_directory = current_version_directory.join("Client2020"); + create_folder_if_not_exists(&client_2020_directory).await; + extract_to_dir(&Client2020Zip, &client_2020_directory); + + info("Finished extracting files, cleaning up."); + std::fs::remove_dir_all(&temp_downloads_directory).unwrap(); // Install the syntax-player scheme in the registry - info!("Installing syntax-player scheme"); + info("Installing syntax-player scheme"); #[cfg(target_os = "windows")] { let hkey_current_user = RegKey::predef(HKEY_CURRENT_USER); - let hkey_classes_root: RegKey = - hkey_current_user.open_subkey("Software\\Classes").unwrap(); + let hkey_classes_root : RegKey = hkey_current_user.open_subkey("Software\\Classes").unwrap(); let hkey_syntax_player = hkey_classes_root.create_subkey("syntax-player").unwrap().0; let hkey_syntax_player_shell = hkey_syntax_player.create_subkey("shell").unwrap().0; - let hkey_syntax_player_shell_open = - hkey_syntax_player_shell.create_subkey("open").unwrap().0; - let hkey_syntax_player_shell_open_command = hkey_syntax_player_shell_open - .create_subkey("command") - .unwrap() - .0; + let hkey_syntax_player_shell_open = hkey_syntax_player_shell.create_subkey("open").unwrap().0; + let hkey_syntax_player_shell_open_command = hkey_syntax_player_shell_open.create_subkey("command").unwrap().0; let defaulticon = hkey_syntax_player.create_subkey("DefaultIcon").unwrap().0; - hkey_syntax_player_shell_open_command - .set_value( - "", - &format!("\"{}\" \"%1\"", current_exe_path.to_str().unwrap()), - ) - .unwrap(); - defaulticon - .set_value("", &format!("\"{}\",0", current_exe_path.to_str().unwrap())) - .unwrap(); - hkey_syntax_player - .set_value("", &format!("URL: Syntax Protocol")) - .unwrap(); + hkey_syntax_player_shell_open_command.set_value("", &format!("\"{}\" \"%1\"", current_exe_path.to_str().unwrap())).unwrap(); + defaulticon.set_value("", &format!("\"{}\",0", current_exe_path.to_str().unwrap())).unwrap(); + hkey_syntax_player.set_value("", &format!("URL: Syntax Protocol")).unwrap(); hkey_syntax_player.set_value("URL Protocol", &"").unwrap(); } #[cfg(not(target_os = "windows"))] { // Linux support // We have to write a .desktop file to ~/.local/share/applications - let desktop_file_path = dirs::data_local_dir() - .unwrap() - .join("applications") - .join("syntax-player.desktop"); + let desktop_file_path = dirs::data_local_dir().unwrap().join("applications").join("syntax-player.desktop"); let desktop_file = format!( - "[Desktop Entry] +"[Desktop Entry] Name=Syntax Launcher Exec={} %u Terminal=true @@ -355,37 +414,31 @@ Icon={} StartupWMClass=SyntaxLauncher Categories=Game; Comment=Syntax Launcher -", - current_exe_path.to_str().unwrap(), - current_exe_path.to_str().unwrap() - ); +", current_exe_path.to_str().unwrap(), current_exe_path.to_str().unwrap()); std::fs::write(desktop_file_path, desktop_file).unwrap(); // We also have to write a mimeapps.list file to ~/.config let mimeapps_list_path = dirs::config_dir().unwrap().join("mimeapps.list"); let mimeapps_list = format!( - "[Default Applications] +"[Default Applications] x-scheme-handler/syntax-player=syntax-player.desktop -" - ); +"); std::fs::write(mimeapps_list_path, mimeapps_list).unwrap(); // We also have to write a mimeapps.list file to ~/.local/share let mimeapps_list_path = dirs::data_local_dir().unwrap().join("mimeapps.list"); let mimeapps_list = format!( - "[Default Applications] +"[Default Applications] x-scheme-handler/syntax-player=syntax-player.desktop -" - ); +"); std::fs::write(mimeapps_list_path, mimeapps_list).unwrap(); } // Write the AppSettings.xml file let app_settings_xml = format!( - " +" content https://{} -", - base_url +", base_url ); std::fs::write(app_settings_path, app_settings_xml).unwrap(); @@ -403,25 +456,17 @@ x-scheme-handler/syntax-player=syntax-player.desktop // Parse the arguments passed to the bootstrapper // Looks something like "syntax-player://1+launchmode:play+gameinfo:TICKET+placelauncherurl:https://www.syntax.eco/Game/placelauncher.ashx?placeId=660&t=TICKET+k:l" - debug!("Arguments Passed: {}", args.join(" ").bright_blue()); + debug(&format!("Arguments Passed: {}", args.join(" ").bright_blue())); if args.len() == 1 { // Just open the website #[cfg(target_os = "windows")] { - std::process::Command::new("cmd") - .arg("/c") - .arg("start") - .arg("https://www.syntax.eco/games") - .spawn() - .unwrap(); + std::process::Command::new("cmd").arg("/c").arg("start").arg("https://www.syntax.eco/games").spawn().unwrap(); std::process::exit(0); } #[cfg(not(target_os = "windows"))] { - std::process::Command::new("xdg-open") - .arg("https://www.syntax.eco/games") - .spawn() - .unwrap(); + std::process::Command::new("xdg-open").arg("https://www.syntax.eco/games").spawn().unwrap(); std::process::exit(0); } } @@ -434,29 +479,30 @@ x-scheme-handler/syntax-player=syntax-player.desktop let mut authentication_ticket = String::new(); let mut join_script = String::new(); let mut client_year = String::new(); - + for arg in main_args { let mut arg_split = arg.split(":"); let key = arg_split.next().unwrap(); - let value = if arg_split.clone().count() > 0 { - arg_split.collect::>().join(":") - } else { - String::new() - }; - debug!("{}: {}", key.bright_blue(), value.bright_blue()); + let value = + if arg_split.clone().count() > 0 { + arg_split.collect::>().join(":") + } else { + String::new() + }; + debug(&format!("{}: {}", key.bright_blue(), value.bright_blue())); match key { "launchmode" => { launch_mode = value.to_string(); - } + }, "gameinfo" => { authentication_ticket = value.to_string(); - } + }, "placelauncherurl" => { join_script = value.to_string(); - } + }, "clientyear" => { client_year = value.to_string(); - } + }, _ => {} } } @@ -468,22 +514,18 @@ x-scheme-handler/syntax-player=syntax-player.desktop let wine_path_file = installation_directory.join("winepath.txt"); if wine_path_file.exists() { let custom_wine = std::fs::read_to_string(wine_path_file).unwrap(); - info!("Using custom wine binary: {}", custom_wine.bright_blue()); + info(&format!("Using custom wine binary: {}", custom_wine.bright_blue())); } else { - info!("No custom wine binary specified, using default wine command"); - info!("If you want to use a custom wine binary, please create a file at {} with the path to the wine binary", wine_path_file.to_str().unwrap()); + info("No custom wine binary specified, using default wine command"); + info(format!("If you want to use a custom wine binary, please create a file at {} with the path to the wine binary", wine_path_file.to_str().unwrap()).as_str()); } } - let client_executable_path: PathBuf; - debug!("{}", &client_year.to_string()); + let client_executable_path : PathBuf; + debug(&client_year.to_string()); if client_year == "2018" { - client_executable_path = current_version_directory - .join("Client2018") - .join("SyntaxPlayerBeta.exe"); + client_executable_path = current_version_directory.join("Client2018").join("SyntaxPlayerBeta.exe"); } else if client_year == "2020" { - client_executable_path = current_version_directory - .join("Client2020") - .join("SyntaxPlayerBeta.exe"); + client_executable_path = current_version_directory.join("Client2020").join("SyntaxPlayerBeta.exe"); } else { client_executable_path = current_version_directory.join("SyntaxPlayerBeta.exe"); } @@ -492,25 +534,17 @@ x-scheme-handler/syntax-player=syntax-player.desktop let app_settings_path = current_version_directory.join("AppSettings.xml"); std::fs::remove_file(app_settings_path).unwrap(); - error!("Failed to run SyntaxPlayerBeta.exe, is your antivirus removing it? The bootstrapper will attempt to redownload the client on next launch."); + error("Failed to run SyntaxPlayerBeta.exe, is your antivirus removing it? The bootstrapper will attempt to redownload the client on next launch."); std::thread::sleep(std::time::Duration::from_secs(20)); std::process::exit(0); } match launch_mode.as_str() { "play" => { - info!("Launching SYNTAX"); + info("Launching SYNTAX"); #[cfg(target_os = "windows")] - { + { let mut command = std::process::Command::new(client_executable_path); - command.args(&[ - "--play", - "--authenticationUrl", - format!("https://{}/Login/Negotiate.ashx", base_url).as_str(), - "--authenticationTicket", - authentication_ticket.as_str(), - "--joinScriptUrl", - format!("{}", join_script.as_str()).as_str(), - ]); + command.args(&["--play","--authenticationUrl", format!("https://{}/Login/Negotiate.ashx", base_url).as_str(), "--authenticationTicket", authentication_ticket.as_str(), "--joinScriptUrl", format!("{}",join_script.as_str()).as_str()]); command.spawn().unwrap(); std::thread::sleep(std::time::Duration::from_secs(5)); std::process::exit(0); @@ -519,27 +553,18 @@ x-scheme-handler/syntax-player=syntax-player.desktop { // We have to launch the game through wine let mut command = std::process::Command::new(custom_wine); - command.args(&[ - client_executable_path.to_str().unwrap(), - "--play", - "--authenticationUrl", - format!("https://{}/Login/Negotiate.ashx", base_url).as_str(), - "--authenticationTicket", - authentication_ticket.as_str(), - "--joinScriptUrl", - format!("{}", join_script.as_str()).as_str(), - ]); + command.args(&[client_executable_path.to_str().unwrap(), "--play","--authenticationUrl", format!("https://{}/Login/Negotiate.ashx", base_url).as_str(), "--authenticationTicket", authentication_ticket.as_str(), "--joinScriptUrl", format!("{}",join_script.as_str()).as_str()]); // We must wait for the game to exit before exiting the bootstrapper let mut child = command.spawn().unwrap(); child.wait().unwrap(); std::thread::sleep(std::time::Duration::from_secs(1)); std::process::exit(0); } - } + }, _ => { - error!("Unknown launch mode, exiting."); + error("Unknown launch mode, exiting."); std::thread::sleep(std::time::Duration::from_secs(10)); std::process::exit(0); } } -} +} \ No newline at end of file diff --git a/src/util/mod.rs b/src/util/mod.rs deleted file mode 100644 index 5927a4c..0000000 --- a/src/util/mod.rs +++ /dev/null @@ -1,159 +0,0 @@ -/* - Move all generic functions into this area -*/ -use colored::*; -use dirs::data_local_dir; -use futures_util::StreamExt; -use md5; -use reqwest::Client; -use reqwest::ClientBuilder; -use std::io::Cursor; -use std::path::PathBuf; -use tokio::fs; -use tokio::fs::create_dir_all; -use zip_extract; - -use crate::constants::*; -use tracing::*; - -#[cfg(not(target_os = "windows"))] -use std::io::prelude::*; -/* -#[cfg(not(target_os = "windows"))] -use std::os::unix::fs::FileExt; -#[cfg(target_os = "windows")] -use std::os::windows::prelude::FileExt; -*/ -#[cfg(target_os = "windows")] -use winreg::enums::*; -#[cfg(target_os = "windows")] -use winreg::RegKey; - -pub async fn http_get(client: &Client, url: &str) -> Result { - debug!("{} {}", "GET".green(), url.bright_blue()); - let response = client.get(url).send().await; - if response.is_err() { - debug!("Failed to fetch {}", url.bright_blue()); - return Err(response.err().unwrap()); - } - let response_body = response.unwrap().text().await.unwrap(); - Ok(response_body) -} - -pub async fn download_file(client: &Client, url: &str) -> Vec { - debug!("{} {}", "GET".green(), url.bright_blue()); - let response = client.get(url).send().await.unwrap(); - /* Why through over a visual bug? */ - let content_length = response.content_length().or(Some(0)).unwrap(); - debug!("Content Length: {}", content_length); - - let time = chrono::Local::now().format("%H:%M:%S").to_string(); - let pg_bar_str = - " {spinner:.green} [{bar:40.cyan/blue}] {bytes}/{total_bytes} ({eta})"; - let progress_bar = indicatif::ProgressBar::new(content_length); - let progress_style = indicatif::ProgressStyle::default_bar() - .template( - format!( - "{}\n{}", - format!( - "[{}] [{}] {}", - time.bold().blue(), - "INFO".bold().green(), - &format!("Downloading {}", &url.bright_blue()) - ), - pg_bar_str - ) - .as_str(), - ) - .unwrap() - .progress_chars("#>-"); - progress_bar.set_style(progress_style); - progress_bar.set_message("Downloading File"); - - let mut buffer: Vec = vec![]; - let mut downloaded: u64 = 0; - let mut stream = response.bytes_stream(); - - while let Some(item) = stream.next().await { - let chunk = item - .or(Err(format!("Error while downloading file"))) - .unwrap(); - - buffer.write_all(chunk.as_ref()).unwrap(); - let new = std::cmp::min(downloaded + (chunk.len() as u64), content_length); - downloaded = new; - progress_bar.set_position(new); - } - progress_bar.finish(); - info!("Finished downloading {}", url.green()); - - return buffer; -} - -pub async fn download_to_file(client: &Client, url: &str, path: &PathBuf) { - let bytes = download_file(client, url).await; - fs::write(path, bytes).await.unwrap(); -} - -pub async fn download_file_prefix>(client: &Client, url: T) -> Vec { - let string: String = url.into(); - let buffer = download_file(client, &string).await; - return buffer; -} - -/* -pub async fn generate_md5(input: &str) -> String { - let hashed_input = md5::compute(input.as_bytes()); - return format!("{:x}", hashed_input); -}*/ -pub async fn create_folder_if_not_exists(path: &PathBuf) { - if !path.exists() { - info!("Creating folder {}", path.to_str().unwrap().bright_blue()); - fs::create_dir_all(path).await.unwrap(); - } -} - -pub fn get_installation_directory() -> PathBuf { - return PathBuf::from(data_local_dir().unwrap().to_str().unwrap()).join("Syntax"); -} - -/* Why was this in the main function i will never know */ -pub async fn extract_to_dir(zip_file: &Vec, target_dir: &PathBuf) { - let zip_file_cursor = Cursor::new(zip_file); - zip_extract::extract(zip_file_cursor, target_dir, false).unwrap(); -} - -fn get_location_from_file_name>(file_name: T) -> String { - let file_name = file_name.as_ref(); - for [first, last] in FILES_TO_DOWNLOAD { - if first == file_name { - return last.to_owned(); - } - } - let formated = format!("Is not a valid file {}", file_name); - error!("{}", formated); - panic!("{}", formated) -} - -pub async fn download_and_extract, T2: Into, P: Into>( - file_name: T, - url_prefix: T2, - extract_location: P, -) { - let http_client = ClientBuilder::default().build().unwrap(); - let file_name: String = file_name.into(); - let url_prefix: String = url_prefix.into(); - let extract_location: PathBuf = extract_location.into(); - - let buffer = download_file_prefix(&http_client, format!("{}{file_name}", url_prefix)).await; - drop(http_client); - drop(url_prefix); - let dir = extract_location.join(get_location_from_file_name(&file_name)); - - create_dir_all(&dir).await.unwrap(); - info!("Extracting file {}", file_name); - extract_to_dir(&buffer, &dir).await; - drop(buffer); - - info!("File {} installed to {:?}", file_name, dir.display()); -}