Initial commit
This commit is contained in:
commit
eff3bccf2b
11 changed files with 5287 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/target
|
4873
Cargo.lock
generated
Normal file
4873
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
9
Cargo.toml
Normal file
9
Cargo.toml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
[package]
|
||||||
|
name = "ZZZ-RR-Launcher"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
eframe = "0.31.1"
|
||||||
|
git2 = "0.20.1"
|
||||||
|
reqwest = {version = "0.12.15", features = ["blocking", "rustls-tls"] }
|
3
src/core/mod.rs
Normal file
3
src/core/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
// Fuck Hadros
|
||||||
|
pub mod zenless;
|
||||||
|
pub mod rustdl;
|
46
src/core/rustdl.rs
Normal file
46
src/core/rustdl.rs
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
// Import std crate
|
||||||
|
use std::process::Command;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
// Funtion for Downloading and installing rust automatically
|
||||||
|
pub fn download_install_rust() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let url = "https://win.rustup.rs/x86_64";
|
||||||
|
let exe_name = "rustup-init.exe";
|
||||||
|
|
||||||
|
let response = reqwest::blocking::get(url)?;
|
||||||
|
let bytes = response.bytes()?;
|
||||||
|
|
||||||
|
fs::write(exe_name, &bytes)?;
|
||||||
|
println!("[✓] Download finished: {}", exe_name);
|
||||||
|
|
||||||
|
let status = Command::new(exe_name)
|
||||||
|
.args(["-y", "--default-toolchain", "nightly"])
|
||||||
|
.status()?;
|
||||||
|
|
||||||
|
if status.success() {
|
||||||
|
println!("[✓] Rust nightly installed!");
|
||||||
|
} else {
|
||||||
|
eprintln!("[✗] Installation has failed.");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Downloading and Install Windows MSVC for building the PS
|
||||||
|
let status_stable = Command::new("rustup")
|
||||||
|
.args(["install", "stable-x86_64-pc-windows-msvc"])
|
||||||
|
.status()?;
|
||||||
|
|
||||||
|
if status_stable.success() {
|
||||||
|
println!("[✓] Rust stable toolchain installed for x86_64-pc-windows-msvc!");
|
||||||
|
} else {
|
||||||
|
eprintln!("[✗] Failed to install stable MSVC toolchain.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleaning
|
||||||
|
if Path::new(exe_name).exists() {
|
||||||
|
fs::remove_file(exe_name)?;
|
||||||
|
println!("[✓] Cleanup finished.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
2
src/core/zenless/mod.rs
Normal file
2
src/core/zenless/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod zenless_srv;
|
||||||
|
pub mod zenless_client_patch;
|
93
src/core/zenless/zenless_client_patch.rs
Normal file
93
src/core/zenless/zenless_client_patch.rs
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
// Import std and git2 crate for downloading, building and launching client part
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
|
use std::fs::File;
|
||||||
|
use std::{fs, path::Path, path::PathBuf};
|
||||||
|
use git2::Repository;
|
||||||
|
|
||||||
|
// download client patch from the distant git repository
|
||||||
|
pub fn zzz_download_client_patch() {
|
||||||
|
|
||||||
|
let zzz_client_dir_path = "zzz_client_patch";
|
||||||
|
if let Err(e) = fs::create_dir_all(zzz_client_dir_path) {
|
||||||
|
eprintln!("Error creating folder: {}", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let zzz_game_dir_path = "zzz_game";
|
||||||
|
if let Err(e) = fs::create_dir_all(zzz_game_dir_path) {
|
||||||
|
eprintln!("Error creating folder: {}", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let repo_url = "https://git.xeondev.com/traffic95/trigger-patch";
|
||||||
|
|
||||||
|
let path = Path::new(zzz_client_dir_path);
|
||||||
|
|
||||||
|
match Repository::clone(repo_url, path) {
|
||||||
|
Ok(_) => {
|
||||||
|
println!("✅ Client Repo cloned at {}", zzz_client_dir_path);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Error during Client Patch downloading : {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the Client from source
|
||||||
|
pub fn zzz_build_client_patch() {
|
||||||
|
|
||||||
|
let client_patch_log_file_path = "client_patch_log.txt";
|
||||||
|
let client_patch_log_file = File::create(client_patch_log_file_path).expect("Unable to create log file");
|
||||||
|
let client_patch_lof_file_clone = client_patch_log_file.try_clone().expect("Unable to clone log file handle");
|
||||||
|
let mut child = Command::new("cargo")
|
||||||
|
.arg("+nightly")
|
||||||
|
.arg("build")
|
||||||
|
.arg("--target")
|
||||||
|
.arg("x86_64-pc-windows-msvc")
|
||||||
|
.current_dir("zzz_client_patch")
|
||||||
|
.stdout(Stdio::from(client_patch_log_file))
|
||||||
|
.stderr(Stdio::from(client_patch_lof_file_clone))
|
||||||
|
.spawn()
|
||||||
|
.expect("❌ Error during the launch of cargo build");
|
||||||
|
|
||||||
|
let status = child.wait().expect("Error during process waiting");
|
||||||
|
|
||||||
|
if status.success() {
|
||||||
|
println!("✅ Client built!");
|
||||||
|
} else {
|
||||||
|
println!("❌ Build failed with status: {}", status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move the exe and dll of the Client patch to the game folder, and launch it
|
||||||
|
pub fn zzz_client_patch_loaded() {
|
||||||
|
let source_dir = PathBuf::from("zzz_client_patch/target/x86_64-pc-windows-msvc/debug");
|
||||||
|
let launcher = source_dir.join("launcher.exe");
|
||||||
|
let dll = source_dir.join("trigger.dll");
|
||||||
|
|
||||||
|
let dest_dir = PathBuf::from("zzz_game");
|
||||||
|
|
||||||
|
fs::copy(&launcher, dest_dir.join("launcher.exe")).expect("Failed to copy launcher.exe");
|
||||||
|
fs::copy(&dll, dest_dir.join("trigger.dll")).expect("Failed to copy trigger.dll");
|
||||||
|
|
||||||
|
let launcher_exec_path = PathBuf::from("zzz_game/launcher.exe");
|
||||||
|
|
||||||
|
if !launcher_exec_path.exists() {
|
||||||
|
eprintln!("❌ launcher.exe not found, Make sure the build was successful and the files were copied.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
match Command::new(&launcher_exec_path)
|
||||||
|
.stdout(Stdio::inherit())
|
||||||
|
.stderr(Stdio::inherit())
|
||||||
|
.spawn()
|
||||||
|
{
|
||||||
|
Ok(mut child) => {
|
||||||
|
println!("launcher.exe is starting");
|
||||||
|
let _ = child.wait();
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("❌ Can't Launch the launcher : {}",e )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
119
src/core/zenless/zenless_srv.rs
Normal file
119
src/core/zenless/zenless_srv.rs
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
// Import std and git2 crate for downloading, building and launching servers part
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
fs::{self, File},
|
||||||
|
io::{BufRead, BufReader},
|
||||||
|
path::Path,
|
||||||
|
process::{Command, Stdio},
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
thread,
|
||||||
|
};
|
||||||
|
|
||||||
|
use git2::Repository;
|
||||||
|
|
||||||
|
// Funtion for download the server from the distant Git repository
|
||||||
|
pub fn zzz_download_server() {
|
||||||
|
let zzz_dir_path = "zzz_ps";
|
||||||
|
if let Err(e) = fs::create_dir_all(zzz_dir_path) {
|
||||||
|
eprintln!("Error creating folder: {}", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let repo_url = "https://git.xeondev.com/traffic95/trigger-rs";
|
||||||
|
let path = Path::new(zzz_dir_path);
|
||||||
|
|
||||||
|
match Repository::clone(repo_url, path) {
|
||||||
|
Ok(_) => println!("✅ Server Repo cloned at {}", zzz_dir_path),
|
||||||
|
Err(e) => eprintln!("❌ Clone failed: {}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function for building the server via cargo command build
|
||||||
|
pub fn zzz_build_server() {
|
||||||
|
let log_path = "server_log.txt";
|
||||||
|
let log_file = File::create(log_path).expect("Cannot create log file");
|
||||||
|
let log_file_clone = log_file.try_clone().expect("Cannot clone log handle");
|
||||||
|
|
||||||
|
let mut child = Command::new("cargo")
|
||||||
|
.arg("build")
|
||||||
|
.current_dir("zzz_ps")
|
||||||
|
.stdout(Stdio::from(log_file))
|
||||||
|
.stderr(Stdio::from(log_file_clone))
|
||||||
|
.spawn()
|
||||||
|
.expect("Error launching build");
|
||||||
|
|
||||||
|
let status = child.wait().expect("Error waiting for process");
|
||||||
|
|
||||||
|
if status.success() {
|
||||||
|
println!("✅ Server built!");
|
||||||
|
} else {
|
||||||
|
println!("❌ Build failed with status: {}", status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// List every servers part in the Launcher UI
|
||||||
|
pub fn list_available_servers() -> Vec<String> {
|
||||||
|
let server_dir = Path::new("zzz_ps/target/debug");
|
||||||
|
let expected = [
|
||||||
|
"trigger-muip-server.exe",
|
||||||
|
"trigger-hall-server.exe",
|
||||||
|
"trigger-gate-server.exe",
|
||||||
|
"trigger-game-server.exe",
|
||||||
|
"trigger-dispatch-server.exe",
|
||||||
|
"trigger-battle-server.exe",
|
||||||
|
];
|
||||||
|
|
||||||
|
expected
|
||||||
|
.iter()
|
||||||
|
.filter_map(|name| {
|
||||||
|
let path = server_dir.join(name);
|
||||||
|
if path.exists() {
|
||||||
|
Some(name.to_string())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Launch the servers inside of every designed tabs with logs
|
||||||
|
pub fn launch_server_with_logs(
|
||||||
|
exe_name: &str,
|
||||||
|
logs: Arc<Mutex<HashMap<String, Vec<String>>>>,
|
||||||
|
) {
|
||||||
|
let path = format!("zzz_ps/target/debug/{}", exe_name);
|
||||||
|
let mut child = match Command::new(&path)
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::piped())
|
||||||
|
.spawn()
|
||||||
|
{
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("❌ Failed to launch {}: {}", exe_name, e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let stdout = child.stdout.take().unwrap();
|
||||||
|
let stderr = child.stderr.take().unwrap();
|
||||||
|
|
||||||
|
let logs_clone = Arc::clone(&logs);
|
||||||
|
let name = exe_name.to_string();
|
||||||
|
thread::spawn(move || {
|
||||||
|
let reader = BufReader::new(stdout);
|
||||||
|
for line in reader.lines().flatten() {
|
||||||
|
let mut logs = logs_clone.lock().unwrap();
|
||||||
|
logs.entry(name.clone()).or_default().push(format!("📤 {}", line));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let logs_clone = Arc::clone(&logs);
|
||||||
|
let name = exe_name.to_string();
|
||||||
|
thread::spawn(move || {
|
||||||
|
let reader = BufReader::new(stderr);
|
||||||
|
for line in reader.lines().flatten() {
|
||||||
|
let mut logs = logs_clone.lock().unwrap();
|
||||||
|
logs.entry(name.clone()).or_default().push(format!("📕 {}", line));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
17
src/main.rs
Normal file
17
src/main.rs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
// Import custom rust file as crate
|
||||||
|
mod ui;
|
||||||
|
mod core;
|
||||||
|
use ui::gui::ZZZRRLauncher;
|
||||||
|
|
||||||
|
// Import eframe crate to make the UI
|
||||||
|
use eframe::NativeOptions;
|
||||||
|
|
||||||
|
// Init the Eframe UI and Launch the launcher
|
||||||
|
fn main() -> eframe::Result<()> {
|
||||||
|
let options = NativeOptions::default();
|
||||||
|
eframe::run_native(
|
||||||
|
"ZZZ-RR-Launcher Beta",
|
||||||
|
options,
|
||||||
|
Box::new(|_cc| Ok(Box::new(ZZZRRLauncher::default()))),
|
||||||
|
)
|
||||||
|
}
|
122
src/ui/gui.rs
Normal file
122
src/ui/gui.rs
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
// Import "std crate" with necessary depedencies for buttons, "Eframe" for UI, And everything needed for the buttons funtions
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use eframe::egui;
|
||||||
|
use crate::core::{rustdl, zenless::zenless_client_patch, zenless::zenless_srv};
|
||||||
|
|
||||||
|
|
||||||
|
// Define the Launcher structures and functionnalities
|
||||||
|
pub struct ZZZRRLauncher {
|
||||||
|
selected_tab: Tab,
|
||||||
|
available_servers: Vec<String>,
|
||||||
|
logs: Arc<Mutex<HashMap<String, Vec<String>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
enum Tab {
|
||||||
|
Download,
|
||||||
|
Server,
|
||||||
|
Zzz,
|
||||||
|
ServerInstance(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ZZZRRLauncher {
|
||||||
|
fn default() -> Self {
|
||||||
|
let logs = Arc::new(Mutex::new(HashMap::new()));
|
||||||
|
let available_servers = zenless_srv::list_available_servers();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
selected_tab: Tab::Download,
|
||||||
|
available_servers,
|
||||||
|
logs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Generate an UI with some buttons, each buttons are different
|
||||||
|
impl eframe::App for ZZZRRLauncher {
|
||||||
|
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||||
|
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
if ui.selectable_label(self.selected_tab == Tab::Download, "📥 Download").clicked() {
|
||||||
|
self.selected_tab = Tab::Download;
|
||||||
|
}
|
||||||
|
if ui.selectable_label(self.selected_tab == Tab::Server, "🛠 Server").clicked() {
|
||||||
|
self.selected_tab = Tab::Server;
|
||||||
|
}
|
||||||
|
if ui.selectable_label(self.selected_tab == Tab::Zzz, "🎮 ZZZ").clicked() {
|
||||||
|
self.selected_tab = Tab::Zzz;
|
||||||
|
}
|
||||||
|
|
||||||
|
for server in &self.available_servers {
|
||||||
|
if ui
|
||||||
|
.selectable_label(
|
||||||
|
self.selected_tab == Tab::ServerInstance(server.clone()),
|
||||||
|
format!("🔌 {}", server),
|
||||||
|
)
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
self.selected_tab = Tab::ServerInstance(server.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
|
match &self.selected_tab {
|
||||||
|
Tab::Download => {
|
||||||
|
ui.heading("Download Tools");
|
||||||
|
if ui.button("Download Server").clicked() {
|
||||||
|
zenless_srv::zzz_download_server();
|
||||||
|
self.available_servers = zenless_srv::list_available_servers();
|
||||||
|
}
|
||||||
|
if ui.button("Download Client Patch").clicked() {
|
||||||
|
zenless_client_patch::zzz_download_client_patch();
|
||||||
|
}
|
||||||
|
if ui.button("Download Rust Nightly").clicked() {
|
||||||
|
let _ = rustdl::download_install_rust();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Tab::Server => {
|
||||||
|
ui.heading("Server Management");
|
||||||
|
if ui.button("Build Server").clicked() {
|
||||||
|
zenless_srv::zzz_build_server();
|
||||||
|
self.available_servers = zenless_srv::list_available_servers();
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
for server in &self.available_servers {
|
||||||
|
if ui.button(format!("🚀 Launch {}", server)).clicked() {
|
||||||
|
zenless_srv::launch_server_with_logs(server, Arc::clone(&self.logs));
|
||||||
|
self.selected_tab = Tab::ServerInstance(server.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Tab::Zzz => {
|
||||||
|
ui.heading("ZZZ Client Patch");
|
||||||
|
if ui.button("Build Client Patch").clicked() {
|
||||||
|
zenless_client_patch::zzz_build_client_patch();
|
||||||
|
}
|
||||||
|
if ui.button("Launch ZZZ").clicked() {
|
||||||
|
zenless_client_patch::zzz_client_patch_loaded();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Tab::ServerInstance(server_name) => {
|
||||||
|
ui.heading(format!("📡 Logs for {}", server_name));
|
||||||
|
if let Some(lines) = self.logs.lock().unwrap().get(server_name) {
|
||||||
|
egui::ScrollArea::vertical().show(ui, |ui| {
|
||||||
|
for line in lines.iter().rev().take(100).rev() {
|
||||||
|
ui.monospace(line);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
ui.label("No logs yet.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
2
src/ui/mod.rs
Normal file
2
src/ui/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
// Fuck Hadros
|
||||||
|
pub mod gui;
|
Loading…
Reference in a new issue