add bootstrap

This commit is contained in:
lx
2026-07-06 13:36:23 -04:00
commit 504fb652a2
12 changed files with 2592 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
[target.'cfg(all(windows, target_env = "msvc"))']
rustflags = ["-C", "target-feature=+crt-static"]
+52
View File
@@ -0,0 +1,52 @@
name: Build Release Executables
on:
release:
types:
- created
jobs:
build-release-windows:
name: Build on Windows ${{ github.event.release.tag_name }}
runs-on: windows-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v2
- uses: actions-rs/cargo@v1
with:
command: build
args: --release
- name: Rename executable
run: mv target/release/syntax_bootstrapper.exe target/release/SyntaxPlayerLauncher.exe
- name: Upload Release Asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ github.event.release.upload_url }}
asset_path: target/release/SyntaxPlayerLauncher.exe
asset_name: SyntaxPlayerLauncher.exe
asset_content_type: application/octet-stream
build-release-linux:
name: Build on Linux ${{ github.event.release.tag_name }}
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v2
- uses: actions-rs/cargo@v1
with:
command: build
args: --release
- name: Rename executable
run: mv target/release/syntax_bootstrapper target/release/SyntaxPlayerLinuxLauncher
- name: Upload Release Asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ github.event.release.upload_url }}
asset_path: target/release/SyntaxPlayerLinuxLauncher
asset_name: SyntaxPlayerLinuxLauncher
asset_content_type: application/octet-stream
+1
View File
@@ -0,0 +1 @@
/target
Generated
+1880
View File
File diff suppressed because it is too large Load Diff
+33
View File
@@ -0,0 +1,33 @@
[package]
name = "syntax_bootstrapper"
version = "1.4.1"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
term_size = "0.3.2"
colored = "2.0.4"
chrono = "0.4.26"
dirs = "5.0.1"
indicatif = "0.17.6"
reqwest = { version = "0.11.20", features = ["stream"]}
futures = "0.3.28"
tokio = { version = "1.32.0", features=["full"]}
futures-util = "0.3.28"
md5 = "0.7.0"
zip-extract = "0.1.2"
sha1 = "0.10.6"
[target.'cfg(windows)'.dependencies]
winreg = "0.51.0"
[build-dependencies]
chrono = "0.4.26"
winres = "0.1.12"
[profile.release]
strip = true
lto = true
opt-level = "z"
codegen-units = 1
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 PrintedScript
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

+31
View File
@@ -0,0 +1,31 @@
use std::fs;
use std::env;
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();
// Write the build date to a file
let out_dir = env::var("OUT_DIR").unwrap();
let dest_path = format!("{}/build_date.txt", out_dir);
fs::write(&dest_path, &build_date).unwrap();
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", "Vortexi Windows Bootstrapper");
res.set("ProductName", "Vortexi Bootstrapper");
res.set("ProductVersion", env!("CARGO_PKG_VERSION"));
res.set("InternalName", "Vortexi Bootstrapper");
res.set("OriginalFilename", "VortexiPlayerLauncher.exe");
res.set("CompanyName", "Vortexi Corporation");
res.set("LegalCopyright", "Copyright (c) 2025 Vortexti Corporation");
res.compile().unwrap();
}
println!("cargo:rerun-if-changed=build.rs");
}
+15
View File
@@ -0,0 +1,15 @@
# Vortexi Boostrapper
A bootstrapper written in Rust meant to replace the old Roblox Launcher
You are welcome to add support to other platforms ( *MacOS* ) or add improvements to the bootstrapper
## Building
For Windows:
> win-build-release.bat
For Linux:
> cargo build --release
If you want to build the debug version of the bootstrapper for development you can run
> cargo build
+546
View File
@@ -0,0 +1,546 @@
use chrono::format;
use colored::*;
use std::path::PathBuf;
use reqwest::Client;
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;
#[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)]
fn debug( message : &str ) {
let time = chrono::Local::now().format("%H:%M:%S").to_string();
println!("[{}] [{}] {}", time.bold().blue(), "DEBUG".bold().yellow(), message);
}
#[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()));
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();
}
}
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("Vortexi");
}
#[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");
}
#[cfg(not(target_os = "windows"))]
{
std::process::Command::new("clear").spawn().unwrap();
}
let args: Vec<String> = std::env::args().collect();
let base_url : &str = "vortexi.cc";
let mut setup_url : &str = "setup.vortexi.cc";
let fallback_setup_url : &str = "d2f3pa9j0u8v6f.cloudfront.net";
let mut bootstrapper_filename :&str = "VortexiPlayerLauncher.exe";
#[cfg(not(target_os = "windows"))]
{
bootstrapper_filename = "VortexiPlayerLinuxLauncher";
}
let build_date = include_str!(concat!(env!("OUT_DIR"), "/build_date.txt"));
let startup_text = format!(r#"
_ __ __ _
| | / /__ ____/ /______ __ (_)
| |/ / _ \/ __/ __/ -_) \ // /
|___/\___/_/ \__/\__/_\_\/_/
{} | 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!("Vortexi 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::<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_green().bold().on_black());
}
// 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.yellow().bold().on_black());
}
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);
}
}
}
}
// 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");
// Download the latest bootstrapper
download_file(&http_client, &format!("https://{}/{}-{}", setup_url, latest_client_version, bootstrapper_filename), &latest_bootstrapper_path).await;
}
// 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(&current_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=Vortexi Launcher
Exec={} %u
Icon={}
Type=Application
Terminal=true
Version={}
MimeType=x-scheme-handler/vortexi-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("vortexi-player.desktop");
std::fs::write(desktop_file_path, desktop_file_content).unwrap();
info("Please launch Vortexi from the website, to continue with the update process.");
std::thread::sleep(std::time::Duration::from_secs(20));
}
std::process::exit(0);
}
}
// 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");
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 != latest_bootstrapper_path {
std::fs::remove_file(path).unwrap();
}
} else {
std::fs::remove_dir_all(path).unwrap();
}
}
let VersionURLPrefix = format!("https://{}/{}-", setup_url, latest_client_version);
let Client2018Zip : PathBuf = download_file_prefix(&http_client, format!("{}client.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);
info("Finished extracting files, cleaning up.");
std::fs::remove_dir_all(&temp_downloads_directory).unwrap();
// Install the vortexi-player scheme in the registry
info("Installing vortexi-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("vortexi-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\"", 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
let desktop_file_content = &format!("[Desktop Entry]
Name=Vortexi Launcher
Exec={} %u
Icon={}
Type=Application
Terminal=true
Version={}
MimeType=x-scheme-handler/vortexi-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("vortexi-player.desktop");
std::fs::write(desktop_file_path, desktop_file_content).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 "vortexi-player://1+launchmode:play+gameinfo:TICKET+placelauncherurl:https://vortexi.cc/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://vortexi.cc/games").spawn().unwrap();
std::process::exit(0);
}
#[cfg(not(target_os = "windows"))]
{
std::process::Command::new("xdg-open").arg("https://vortexi.cc/games").spawn().unwrap();
std::process::exit(0);
}
}
let main_args = &args[1];
let main_args = main_args.replace("vortexi-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());
client_executable_path = current_version_directory.join("Client2018").join("VortexiPlayerBeta.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 VortexiPlayerBeta.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 Vortexi");
#[cfg(target_os = "windows")]
{
let mut command = std::process::Command::new(client_executable_path);
if client_year == "2015" {
// Use the 2015 format with -a, -j, -t arguments
command.args(&[
"-a", format!("http://{}/Login/Negotiate.ashx", base_url).as_str(),
"-j", join_script.as_str(),
"-t", authentication_ticket.as_str()
]);
} else {
// Use the standard format for all other clients
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);
if client_year == "2015" {
// Use the 2015 format with -a, -j, -t arguments for wine too
command.args(&[
client_executable_path.to_str().unwrap(),
"-a", format!("http://{}/Login/Negotiate.ashx", base_url).as_str(),
"-j", join_script.as_str(),
"-t", authentication_ticket.as_str()
]);
} else {
// Use the standard format for all other clients
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);
}
}
}
BIN
View File
Binary file not shown.
+11
View File
@@ -0,0 +1,11 @@
@echo off
rustup target add i686-pc-windows-msvc
:: 32-bit build
cargo build --release --target=i686-pc-windows-msvc
:: Compression disabled as it gave some false positives on virustotal
:: May re-enable in the future if we ever get a signing certificate
:: tools\upx.exe target\release\*.exe
:: Renames the executable to SyntaxPlayerLauncher.exe from syntax_bootstrapper.exe
ren target\i686-pc-windows-msvc\release\syntax_bootstrapper.exe SyntaxPlayerLauncher.exe