|
|
|
|
@ -1,3 +1,4 @@
|
|
|
|
|
use chrono::format;
|
|
|
|
|
use colored::*;
|
|
|
|
|
use std::path::PathBuf;
|
|
|
|
|
use reqwest::Client;
|
|
|
|
|
@ -5,6 +6,7 @@ use dirs::data_local_dir;
|
|
|
|
|
use futures_util::StreamExt;
|
|
|
|
|
use md5;
|
|
|
|
|
use zip_extract;
|
|
|
|
|
use sha1::{Sha1, Digest};
|
|
|
|
|
|
|
|
|
|
#[cfg(target_os = "windows")]
|
|
|
|
|
use std::os::windows::prelude::FileExt;
|
|
|
|
|
@ -111,6 +113,14 @@ pub async fn create_folder_if_not_exists( path: &PathBuf ) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn get_sha1_hash_of_file( path: &PathBuf ) -> String {
|
|
|
|
|
let mut file = std::fs::File::open(path).unwrap();
|
|
|
|
|
let mut hasher = Sha1::new();
|
|
|
|
|
std::io::copy(&mut file, &mut hasher).unwrap();
|
|
|
|
|
let hash = hasher.finalize();
|
|
|
|
|
return format!("{:x}", hash);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn get_installation_directory() -> PathBuf {
|
|
|
|
|
return PathBuf::from(data_local_dir().unwrap().to_str().unwrap()).join("Syntax");
|
|
|
|
|
}
|
|
|
|
|
@ -240,28 +250,63 @@ async fn main() {
|
|
|
|
|
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");
|
|
|
|
|
// 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);
|
|
|
|
|
command.args(&args[1..]);
|
|
|
|
|
command.spawn().unwrap();
|
|
|
|
|
}
|
|
|
|
|
#[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();
|
|
|
|
|
// Lets compare the SHA1 hash of the latest bootstrapper to the one we are currently running
|
|
|
|
|
// If they are the same, then we can continue with the update process
|
|
|
|
|
// We do this because antivirus software does not like this type of behavior
|
|
|
|
|
|
|
|
|
|
let latest_bootstrapper_hash = get_sha1_hash_of_file(&latest_bootstrapper_path).await;
|
|
|
|
|
let current_exe_hash = get_sha1_hash_of_file(¤t_exe_path).await;
|
|
|
|
|
|
|
|
|
|
debug(&format!("Latest Bootstrapper Hash: {}", latest_bootstrapper_hash.bright_blue()));
|
|
|
|
|
debug(&format!("Current Bootstrapper Hash: {}", current_exe_hash.bright_blue()));
|
|
|
|
|
|
|
|
|
|
if latest_bootstrapper_hash != current_exe_hash {
|
|
|
|
|
info("Starting latest bootstrapper");
|
|
|
|
|
// 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();
|
|
|
|
|
|
|
|
|
|
let desktop_file_content = &format!("[Desktop Entry]
|
|
|
|
|
Name=Syntax Launcher
|
|
|
|
|
Exec={} %u
|
|
|
|
|
Icon={}
|
|
|
|
|
Type=Application
|
|
|
|
|
Terminal=true
|
|
|
|
|
Version={}
|
|
|
|
|
MimeType=x-scheme-handler/syntax-player;", latest_bootstrapper_path.to_str().unwrap(), latest_bootstrapper_path.to_str().unwrap(), env!("CARGO_PKG_VERSION"));
|
|
|
|
|
|
|
|
|
|
let desktop_file_path = dirs::data_local_dir().unwrap().join("applications").join("syntax-player.desktop");
|
|
|
|
|
std::fs::write(desktop_file_path, desktop_file_content).unwrap();
|
|
|
|
|
|
|
|
|
|
info("Please launch SYNTAX from the website, to continue with the update process.");
|
|
|
|
|
std::thread::sleep(std::time::Duration::from_secs(20));
|
|
|
|
|
}
|
|
|
|
|
std::process::exit(0);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
std::process::exit(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Looks like we are running from the latest version directory, so we can continue with the update process
|
|
|
|
|
@ -269,14 +314,13 @@ async fn main() {
|
|
|
|
|
// 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() {
|
|
|
|
|
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(¤t_version_directory).unwrap() {
|
|
|
|
|
let entry = entry.unwrap();
|
|
|
|
|
let path = entry.path();
|
|
|
|
|
if path.is_file() {
|
|
|
|
|
if path != current_exe_path {
|
|
|
|
|
if path != latest_bootstrapper_path {
|
|
|
|
|
std::fs::remove_file(path).unwrap();
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
@ -285,85 +329,35 @@ async fn main() {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
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 ShadersZip : PathBuf = download_file_prefix(&http_client, format!("{}shaders.zip", VersionURLPrefix).as_str(), &temp_downloads_directory).await;
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
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");
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
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");
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
|
|
|
|
|
@ -378,43 +372,25 @@ async fn main() {
|
|
|
|
|
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_shell_open_command.set_value("", &format!("\"{}\" \"%1\"", latest_bootstrapper_path.to_str().unwrap())).unwrap();
|
|
|
|
|
defaulticon.set_value("", &format!("\"{}\",0", latest_bootstrapper_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]
|
|
|
|
|
let desktop_file_content = &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();
|
|
|
|
|
Type=Application
|
|
|
|
|
Terminal=true
|
|
|
|
|
Version={}
|
|
|
|
|
MimeType=x-scheme-handler/syntax-player;", latest_bootstrapper_path.to_str().unwrap(), latest_bootstrapper_path.to_str().unwrap(), env!("CARGO_PKG_VERSION"));
|
|
|
|
|
|
|
|
|
|
let desktop_file_path = dirs::data_local_dir().unwrap().join("applications").join("syntax-player.desktop");
|
|
|
|
|
std::fs::write(desktop_file_path, desktop_file_content).unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Write the AppSettings.xml file
|
|
|
|
|
@ -422,7 +398,7 @@ x-scheme-handler/syntax-player=syntax-player.desktop
|
|
|
|
|
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>
|
|
|
|
|
<Settings>
|
|
|
|
|
<ContentFolder>content</ContentFolder>
|
|
|
|
|
<BaseUrl>https://{}</BaseUrl>
|
|
|
|
|
<BaseUrl>http://{}</BaseUrl>
|
|
|
|
|
</Settings>", base_url
|
|
|
|
|
);
|
|
|
|
|
std::fs::write(app_settings_path, app_settings_xml).unwrap();
|
|
|
|
|
@ -509,8 +485,12 @@ x-scheme-handler/syntax-player=syntax-player.desktop
|
|
|
|
|
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("SyntaxPlayerBeta.exe");
|
|
|
|
|
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
|
|
|
|
|
|