Added build config

This commit is contained in:
mojavemf 2024-01-09 18:19:02 +00:00
parent d8407adf4c
commit fbf1b9241b
7 changed files with 395 additions and 480 deletions

73
Cargo.lock generated
View File

@ -313,6 +313,12 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "equivalent"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "errno"
version = "0.3.2"
@ -508,7 +514,7 @@ dependencies = [
"futures-sink",
"futures-util",
"http",
"indexmap",
"indexmap 1.9.3",
"slab",
"tokio",
"tokio-util",
@ -521,6 +527,12 @@ version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "hashbrown"
version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
[[package]]
name = "hermit-abi"
version = "0.3.2"
@ -647,7 +659,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
dependencies = [
"autocfg",
"hashbrown",
"hashbrown 0.12.3",
]
[[package]]
name = "indexmap"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
dependencies = [
"equivalent",
"hashbrown 0.14.3",
]
[[package]]
@ -980,18 +1002,18 @@ checksum = "f32154ba0af3a075eefa1eda8bb414ee928f62303a54ea85b8d6638ff1a6ee9e"
[[package]]
name = "proc-macro2"
version = "1.0.66"
version = "1.0.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.32"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
dependencies = [
"proc-macro2",
]
@ -1135,18 +1157,18 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.186"
version = "1.0.195"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f5db24220c009de9bd45e69fb2938f4b6d2df856aa9304ce377b3180f83b7c1"
checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.186"
version = "1.0.195"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ad697f7e0b65af4983a4ce8f56ed5b357e8d3c36651bf6a7e13639c17b8e670"
checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c"
dependencies = [
"proc-macro2",
"quote",
@ -1176,6 +1198,19 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_yaml"
version = "0.9.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1bf28c79a99f70ee1f1d83d10c875d2e70618417fda01ad1785e027579d9d38"
dependencies = [
"indexmap 2.1.0",
"itoa",
"ryu",
"serde",
"unsafe-libyaml",
]
[[package]]
name = "sha1"
version = "0.10.5"
@ -1250,9 +1285,9 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
[[package]]
name = "syn"
version = "2.0.28"
version = "2.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567"
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
dependencies = [
"proc-macro2",
"quote",
@ -1271,9 +1306,11 @@ dependencies = [
"indicatif",
"md5",
"reqwest",
"serde",
"serde_yaml",
"term_size",
"tokio",
"winreg 0.51.0",
"winreg 0.52.0",
"winres",
"zip-extract",
]
@ -1492,6 +1529,12 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
[[package]]
name = "unsafe-libyaml"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b"
[[package]]
name = "url"
version = "2.4.0"
@ -1800,9 +1843,9 @@ dependencies = [
[[package]]
name = "winreg"
version = "0.51.0"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "937f3df7948156640f46aacef17a70db0de5917bda9c92b0f751f3a955b588fc"
checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5"
dependencies = [
"cfg-if",
"windows-sys 0.48.0",

View File

@ -11,19 +11,21 @@ colored = "2.0.4"
chrono = "0.4.26"
dirs = "5.0.1"
indicatif = "0.17.6"
reqwest = { version = "0.11.20", features = ["stream"]}
reqwest = { version = "0.11.20", features = ["stream"] }
futures = "0.3.28"
tokio = { version = "1.32.0", features=["full"]}
tokio = { version = "1.32.0", features = ["full"] }
futures-util = "0.3.28"
md5 = "0.7.0"
zip-extract = "0.1.2"
[target.'cfg(windows)'.dependencies]
winreg = "0.51.0"
winreg = "0.52.0"
[build-dependencies]
chrono = "0.4.26"
winres = "0.1.12"
serde = { version = "1.0.195", features = ["derive"] }
serde_yaml = "0.9.30"
[profile.release]
strip = true

9
assets/ascii.txt Normal file
View File

@ -0,0 +1,9 @@
.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

175
build.rs
View File

@ -1,30 +1,161 @@
use std::collections::HashMap;
use std::fs;
use std::env;
use std::path::PathBuf;
use winres::WindowsResource;
fn main() {
// Get the current build date and time
let build_date = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC").to_string();
use serde::Deserialize;
// Write the build date to a file
/// CHANGES
/// [+] Use rust CFG for build (not sure)
/// [+] A build config
/// [+] Ascii is now premade due to the result always being static anyways
/// [+] Cut down into functions
const ENGLISH_LANGUAGE: u16 = 0x0409;
#[derive(Deserialize)]
struct BuildConfig {
#[serde(rename = "Ascii")]
pub ascii: AsciiConfig,
#[serde(rename = "Runtime")]
pub runtime: RuntimeConfig,
#[serde(rename = "Windows")]
pub windows: WindowsConfig,
}
#[derive(Deserialize)]
struct AsciiConfig {
#[serde(rename = "Location")]
pub location: String,
#[serde(rename = "Padding")]
pub padding: usize,
#[serde(rename = "FallbackText")]
pub fallback_text: String,
}
#[derive(Deserialize)]
struct RuntimeConfig {
#[serde(rename = "SetupUrl")]
pub setup_url: String,
#[serde(rename = "BaseUrl")]
pub base_url: String,
#[serde(rename = "ThreadCount")]
pub thread_count: usize,
}
#[derive(Deserialize)]
struct WindowsConfig {
#[serde(rename = "Icon")]
pub icon: String,
#[serde(rename = "Resources")]
pub resources: HashMap<String, String>,
}
fn get_padding(size: usize) -> String {
" ".repeat(size)
}
fn out_dir() -> PathBuf {
let out_dir = env::var("OUT_DIR").unwrap();
let dest_path = format!("{}/build_date.txt", out_dir);
fs::write(&dest_path, &build_date).unwrap();
return PathBuf::from(out_dir);
}
if env::var_os("CARGO_CFG_WINDOWS").is_some() {
// Add a version resource to the executable
let mut res = WindowsResource::new();
res.set_icon("assets/Bootstrapper.ico");
res.set_language(0x0409); // US English
res.set("FileVersion", env!("CARGO_PKG_VERSION"));
res.set("FileDescription", "SYNTAX Windows Bootstrapper");
res.set("ProductName", "SYNTAX Bootstrapper");
res.set("ProductVersion", env!("CARGO_PKG_VERSION"));
res.set("InternalName", "SYNTAX Bootstrapper");
res.set("OriginalFilename", "SyntaxPlayerLauncher.exe");
res.set("CompanyName", "SYNTAX Corporation");
res.set("LegalCopyright", "Copyright (c) 2023");
res.compile().unwrap();
/* Builds ascii that is displayed at startup */
fn build_large_ascii(config: &BuildConfig) {
println!("cargo:rerun-if-changed={}", config.ascii.location);
let build_date = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC").to_string();
let ascii_b = fs::read(&config.ascii.location).unwrap();
let padding = get_padding(config.ascii.padding);
let mut ascii = String::from_utf8(ascii_b).unwrap();
ascii += "\n";
ascii += &format!(
"{} | Build Date: {} | Version: {}",
config.runtime.base_url,
build_date,
env!("CARGO_PKG_VERSION")
);
let mut new_asci = String::new();
for line in ascii.lines() {
new_asci += &format!("{}{}\n", padding.clone(), line);
}
fs::write(out_dir().join("./ascii.txt"), new_asci).unwrap();
}
fn build_small_ascii(config: &BuildConfig) {
let build_date = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC").to_string();
fs::write(
out_dir().join("./ascii_small.txt"),
format!(
"{} | {} | Build Date: {} | Version: {}",
config.ascii.fallback_text,
config.runtime.base_url,
build_date,
env!("CARGO_PKG_VERSION")
)
).unwrap()
}
fn build_ascii(config: &BuildConfig) {
build_large_ascii(config);
build_small_ascii(config);
}
fn build_windows(cfg: &WindowsConfig) {
println!("cargo:rerun-if-changed={}", cfg.icon);
let mut res = WindowsResource::new();
res.set_language(ENGLISH_LANGUAGE); // US English
res.set_icon(&cfg.icon);
for (key, value) in &cfg.resources {
if value.starts_with("env:") {
let (_, env) = value.split_at(4);
let value = env::var(env).unwrap();
res.set(key, &value);
println!("cargo:rerun-if-env-changed={}", env);
} else {
res.set(key, value);
}
}
println!("cargo:rerun-if-changed=build.rs");
}
res.compile().unwrap()
}
fn generate_const<T: AsRef<str>>(name: &str, c_type: &str, value: T) -> String {
let value = match c_type {
"&str" => format!(r#""{}""#, value.as_ref()),
_ => value.as_ref().into(),
};
format!("const {name}: {c_type} = {value};\n")
}
/* Passes values */
fn build_runtime(config: &RuntimeConfig) {
let mut output = String::new();
output += &generate_const("SETUP_URL", "&str", &config.setup_url);
output += &generate_const("BASE_URL", "&str", &config.base_url);
output += &generate_const("THREAD_COUNT", "usize", format!("{}", config.thread_count));
fs::write(out_dir().join("./codegen.rs"), output).unwrap();
}
fn load_config() -> BuildConfig {
println!("cargo:rerun-if-changed=build.yaml");
let bytes = fs::read("./build.yaml").unwrap();
serde_yaml::from_slice(&bytes).unwrap()
}
fn main() {
let config = load_config();
build_ascii(&config);
build_runtime(&config.runtime);
if let Some(_) = env::var_os("CARGO_CFG_WINDOWS") {
build_windows(&config.windows)
}
}

24
build.yaml Normal file
View File

@ -0,0 +1,24 @@
# Information about the startup ascii
Ascii:
Location: assets/ascii.txt
Padding: 4
FallbackText: SYNTAX Bootstrapper
# Information to be used at runtime
Runtime:
SetupUrl: setup.syntax.eco
BaseUrl: syntax.eco
ThreadCount: 2
# Information about the exe
Windows:
Icon: assets/Bootstrapper.ico
Resources:
FileVersion: env:CARGO_PKG_VERSION
FileDescription: SYNTAX Windows Bootstrapper
ProductName: SYNTAX Bootstrapper
ProductVersion: env:CARGO_PKG_VERSION
InternalName: SYNTAX Bootstrapper
OriginalFilename: SyntaxPlayerLauncher.exe
CompanyName: SYNTAX Corporation
LegalCopyright: Copyright (c) 2024

92
src/log/mod.rs Normal file
View File

@ -0,0 +1,92 @@
use std::{ result, error::Error };
use colored::*;
type Result<T> = result::Result<T, Box<dyn Error>>;
pub fn get_time() -> String {
chrono::Local::now().format("%H:%M:%S").to_string()
}
fn get_progress_style<T: AsRef<str>>(target: T) -> Result<ProgressStyle> {
let progress_style = indicatif::ProgressStyle
::default_bar()
.template(
format!(
"{}\n{}",
format!(
"[{}] [{}] {}",
get_time().bold().blue(),
"INFO".bold().green(),
format!("Downloading {}", target.as_ref())
),
" ".repeat(16) +
"{spinner:.green} [{bar:40.cyan/blue}] {bytes}/{total_bytes} ({eta})"
).as_str()
)?
.progress_chars("#>-");
Ok(progress_style)
}
pub fn create_progress() {}
#[macro_export]
macro_rules! info {
($($t:tt)*) => {
{
use ::colored::*;
use crate::log::get_time;
println!( "[{}] [{}] {}",get_time().bold().blue(),"INFO".bold().green(),format!($($t)*));
}
};
}
#[macro_export]
macro_rules! error {
($($t:tt)*) => {
{
use ::colored::*;
use crate::log::get_time;
println!( "[{}] [{}] {}",get_time().bold().blue(),"ERROR".bold().red(),format!($($t)*));
}
};
}
#[cfg(debug_assertions)]
#[macro_export]
macro_rules! debug {
($($t:tt)*) => {
{
use ::colored::*;
use crate::log::get_time;
println!( "[{}] [{}] {}",get_time().bold().blue(),"DEBUG".bold().yellow(),format!($($t)*));
}
};
}
#[cfg(not(debug_assertions))]
#[macro_export]
macro_rules! debug {
($($t:tt)*) => {
{
}
};
}
#[macro_export]
macro_rules! fatal {
($($t:tt)*) => {
{
use ::colored::*;
use crate::log::get_time;
panic!( "[{}] [{}] {}",get_time().bold().blue(),"FATAL".bold().red().underline(),format!($($t)*));
}
};
}
use indicatif::ProgressStyle;
pub(crate) use info;
pub(crate) use error;
pub(crate) use debug;
pub(crate) use fatal;

View File

@ -1,5 +1,6 @@
use chrono::format;
use colored::*;
use std::io::Write;
use std::path::PathBuf;
use reqwest::Client;
use dirs::data_local_dir;
@ -18,96 +19,66 @@ 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);
}
pub mod log;
fn error( message : &str ) {
let time = chrono::Local::now().format("%H:%M:%S").to_string();
println!("[{}] [{}] {}", time.bold().blue(), "ERROR".bold().red(), message);
}
include!(concat!(env!("OUT_DIR"), "/codegen.rs"));
#[cfg(debug_assertions)]
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!(concat!(env!("OUT_DIR"), "./ascii.txt"));
const SMALL_ACII: &str = include_str!(concat!(env!("OUT_DIR"), "./ascii_small.txt"));
#[cfg(not(debug_assertions))]
fn debug( message : &str ) {}
pub async fn http_get( client: &Client ,url: &str ) -> Result<String, reqwest::Error> {
debug(&format!("{} {}", "GET".green(), url.bright_blue()));
pub async fn http_get(client: &Client, url: &str) -> Result<String, reqwest::Error> {
debug!("{} {}", "GET".green(), url.bright_blue());
let response = client.get(url).send().await;
if (response.is_err()) {
debug(&format!("Failed to fetch {}", url.bright_blue()));
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, path: &PathBuf ) {
debug(&format!("{} {}", "GET".green(), url.bright_blue()));
pub async fn download_file(client: &Client, url: &str, path: &PathBuf) {
debug!("{} {}", "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));
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 pg_bar_str = " ";
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_style(progress_style);
progress_bar.set_message("Downloading File");
let file = std::fs::File::create(path).unwrap();
let mut 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();
}
file.write_all(&chunk);
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());
info!("Finished downloading {}", url.green());
}
pub async fn download_file_prefix( client: &Client, url: &str, path_prefix : &PathBuf ) -> PathBuf {
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 {
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 ) {
pub async fn create_folder_if_not_exists(path: &PathBuf) {
if !path.exists() {
info(&format!("Creating folder {}", path.to_str().unwrap().bright_blue()));
info!("Creating folder {}", path.to_str().unwrap().bright_blue());
std::fs::create_dir_all(path).unwrap();
}
}
@ -116,403 +87,46 @@ fn get_installation_directory() -> PathBuf {
return PathBuf::from(data_local_dir().unwrap().to_str().unwrap()).join("Syntax");
}
#[tokio::main]
async fn main() {
fn format_info_line<T: AsRef<str>>(data: T) -> ColoredString {
let data = data.as_ref();
// 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");
}
#[cfg(not(target_os = "windows"))]
{
std::process::Command::new("clear").spawn().unwrap();
}
data.magenta().cyan().italic().on_black()
}
let args: Vec<String> = 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";
#[cfg(not(target_os = "windows"))]
{
bootstrapper_filename = "SyntaxPlayerLinuxLauncher";
}
let build_date = include_str!(concat!(env!("OUT_DIR"), "/build_date.txt"));
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;
if let Some((w, _h)) = term_size::dimensions() {
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
fn is_large() -> bool {
if let Some((terminal_width, _)) = term_size::dimensions() {
return terminal_width > 80;
} else {
let startup_text_lines = startup_text.lines().collect::<Vec<&str>>();
//println!("{}", startup_text.bold().blue().on_black());
// print all lines except the last one
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());
}
return false;
};
}
// print last line as a different color
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());
fn startup_info() {
/* Clear screen */
print!("\x1b[2J\x1b[H");
if !is_large() {
return println!("{}", format_info_line(SMALL_ACII));
}
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(&format!("Latest Client Version: {}", latest_client_version_result.bright_blue()));
latest_client_version = latest_client_version_result;
},
Err(e) => {
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(&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(&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);
}
}
}
let mut lines: Vec<&str> = ASCII_ART.lines().collect();
let last = lines.pop();
for value in lines {
println!("{}", value.bright_magenta().italic().on_black());
}
// Wait for the latest client version to be fetched
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(&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(&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(&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(&format!("Current Version Directory: {}", current_version_directory.to_str().unwrap().bright_blue()));
create_folder_if_not_exists(&current_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(&current_version_directory) {
// Check if the latest bootstrapper is downloaded
if !latest_bootstrapper_path.exists() {
info("Downloading the latest bootstrapper and restarting");
// Download the latest bootstrapper
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")]
{
let mut command = std::process::Command::new(latest_bootstrapper_path.clone());
command.args(&args[1..]);
match command.spawn() {
Ok(_) => {},
Err(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;
command.spawn().expect("Bootstrapper is still corrupted.");
std::thread::sleep(std::time::Duration::from_secs(20));
}
}
}
#[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();
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();
}
std::process::exit(0);
if let Some(last_val) = last {
println!("{}", format_info_line(last_val));
}
}
// 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
// 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.");
for entry in std::fs::read_dir(&current_version_directory).unwrap() {
let entry = entry.unwrap();
let path = entry.path();
if path.is_file() {
if path != current_exe_path {
std::fs::remove_file(path).unwrap();
}
} else {
std::fs::remove_dir_all(path).unwrap();
}
}
fn main() {
/* Ansi character to clear line */
let VersionURLPrefix = format!("https://{}/{}-", setup_url, latest_client_version);
startup_info();
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;
let Client2014Zip : PathBuf = download_file_prefix(&http_client, format!("{}2014client.zip", VersionURLPrefix).as_str(), &temp_downloads_directory).await;
let Client2016Zip : PathBuf = download_file_prefix(&http_client, format!("{}2016client.zip", VersionURLPrefix).as_str(), &temp_downloads_directory).await;
info("Download finished, extracting files.");
fn extract_to_dir( zip_file : &PathBuf, target_dir : &PathBuf ) {
info(format!("Extracting {} to {}", zip_file.to_str().unwrap().bright_blue(), target_dir.to_str().unwrap().bright_blue()).as_str());
let zip_file_cursor = std::fs::File::open(zip_file).unwrap();
zip_extract::extract(zip_file_cursor, target_dir, false).unwrap();
}
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);
let client_2014_directory = current_version_directory.join("Client2014");
create_folder_if_not_exists(&client_2014_directory).await;
extract_to_dir(&Client2014Zip, &client_2014_directory);
let client_2016_directory = current_version_directory.join("Client2016");
create_folder_if_not_exists(&client_2016_directory).await;
extract_to_dir(&Client2016Zip, &client_2016_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");
#[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_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 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.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 = format!(
"[Desktop Entry]
Name=Syntax Launcher
Exec={} %u
Terminal=true
Type=Application
MimeType=x-scheme-handler/syntax-player;
Icon={}
StartupWMClass=SyntaxLauncher
Categories=Game;
Comment=Syntax Launcher
", 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]
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]
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!(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<Settings>
<ContentFolder>content</ContentFolder>
<BaseUrl>http://{}</BaseUrl>
</Settings>", base_url
);
std::fs::write(app_settings_path, app_settings_xml).unwrap();
// Check for any other version directories and deletes them
for entry in std::fs::read_dir(&versions_directory).unwrap() {
let entry = entry.unwrap();
let path = entry.path();
if path.is_dir() {
if path != current_version_directory {
std::fs::remove_dir_all(path).unwrap();
}
}
}
}
// 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(&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::exit(0);
}
#[cfg(not(target_os = "windows"))]
{
std::process::Command::new("xdg-open").arg("https://www.syntax.eco/games").spawn().unwrap();
std::process::exit(0);
}
}
let main_args = &args[1];
let main_args = main_args.replace("syntax-player://", "");
let main_args = main_args.split("+").collect::<Vec<&str>>();
let mut launch_mode = String::new();
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::<Vec<&str>>().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();
},
_ => {}
}
}
let custom_wine = "wine";
#[cfg(not(target_os = "windows"))]
{
// We allow user to specify the wine binary path in installation_directory/winepath.txt
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(&format!("Using custom wine binary: {}", custom_wine.bright_blue()));
} else {
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());
if client_year == "2018" {
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");
} else if client_year == "2014" {
client_executable_path = current_version_directory.join("Client2014").join("SyntaxPlayerBeta.exe");
} else {
client_executable_path = current_version_directory.join("Client2016").join("SyntaxPlayerBeta.exe");
}
if !client_executable_path.exists() {
// Delete AppSettings.xml so the bootstrapper will download the client again
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.");
std::thread::sleep(std::time::Duration::from_secs(20));
std::process::exit(0);
}
match launch_mode.as_str() {
"play" => {
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.spawn().unwrap();
std::thread::sleep(std::time::Duration::from_secs(5));
std::process::exit(0);
}
#[cfg(not(target_os = "windows"))]
{
// 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()]);
// 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.");
std::thread::sleep(std::time::Duration::from_secs(10));
std::process::exit(0);
}
}
}
log::info!("{}", SETUP_URL);
log::debug!("Hello {}", "World");
log::error!("Oh no there was an issue");
log::fatal!("Oh shit")
}