Initial commit

This commit is contained in:
UTAKERDEV 2025-05-08 20:19:14 +02:00
commit eff3bccf2b
11 changed files with 5287 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

4873
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

9
Cargo.toml Normal file
View 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
View file

@ -0,0 +1,3 @@
// Fuck Hadros
pub mod zenless;
pub mod rustdl;

46
src/core/rustdl.rs Normal file
View 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
View file

@ -0,0 +1,2 @@
pub mod zenless_srv;
pub mod zenless_client_patch;

View 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 )
}
}
}

View 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
View 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
View 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
View file

@ -0,0 +1,2 @@
// Fuck Hadros
pub mod gui;