183 lines
No EOL
7.7 KiB
Rust
183 lines
No EOL
7.7 KiB
Rust
use std::env;
|
|
use std::fs;
|
|
use std::io::Write;
|
|
use std::path::Path;
|
|
use std::process::{Command, Stdio};
|
|
|
|
use reqwest::Client;
|
|
use directories::ProjectDirs;
|
|
|
|
const POSTGRES_URL: &str = "https://sbp.enterprisedb.com/getfile.jsp?fileid=1259603";
|
|
const GIT_URL: &str = "https://github.com/git-for-windows/git/releases/download/v2.50.1.windows.1/Git-2.50.1-64-bit.exe";
|
|
const PROTOC_RELEASES_API: &str = "https://api.github.com/repos/protocolbuffers/protobuf/releases/latest";
|
|
const PROTOC_ASSET_KEYWORD: &str = "protoc-31.1-win64.zip";
|
|
const WICKED_WAIFUS_REPO: &str = "https://git.xeondev.com/wickedwaifus/wicked-waifus-rs.git";
|
|
const RUSTUP_URL: &str = "https://static.rust-lang.org/rustup/dist/x86_64-pc-windows-msvc/rustup-init.exe";
|
|
|
|
/// Steps:
|
|
/// 0. Download and install Rust
|
|
/// 1. Download and install `PostgreSQL` 16.9 (user handles admin prompt)
|
|
/// 2. Download and install Protoc, add bin to PATH
|
|
/// 3. Download and install Git
|
|
/// 4. Clone wicked-waifus-rs repo recursively
|
|
/// 5. Build all required binaries in sequence
|
|
pub async fn install_wuthering_waves() -> Result<(), String> {
|
|
let client = Client::new();
|
|
let proj_dirs = ProjectDirs::from("com", "ReversedRooms", "RoomsLauncher").ok_or("Could not get ProjectDirs")?;
|
|
let deps_dir = proj_dirs.data_dir().join("kurogames\\wuwa\\dependencies");
|
|
std::fs::create_dir_all(&deps_dir).map_err(|e| e.to_string())?;
|
|
|
|
// 0. Download and install Rust
|
|
let rustup_installer = deps_dir.join("rustup-init.exe");
|
|
download_file(&client, RUSTUP_URL, &rustup_installer).await?;
|
|
let mut rustup_cmd = Command::new(&rustup_installer);
|
|
rustup_cmd.arg("-y");
|
|
let mut child = rustup_cmd.spawn().map_err(|e| format!("Failed to run rustup-init: {e}"))?;
|
|
let status = child.wait().map_err(|e| format!("Failed to wait for rustup-init: {e}"))?;
|
|
if !status.success() {
|
|
return Err("rustup-init failed".into());
|
|
}
|
|
println!("Rust installed");
|
|
std::fs::remove_file(&rustup_installer).ok();
|
|
|
|
// 1. Download and install PostgreSQL (user handles admin prompt)
|
|
let postgres_installer = deps_dir.join("postgresql-installer.exe");
|
|
download_file(&client, POSTGRES_URL, &postgres_installer).await?;
|
|
run_installer(&postgres_installer, false)?; // not silent
|
|
println!("PostgreSQL installed");
|
|
std::fs::remove_file(&postgres_installer).ok();
|
|
|
|
// 2. Download and install Protoc, add bin to PATH
|
|
let protoc_zip = deps_dir.join("protoc.zip");
|
|
let protoc_dir = deps_dir.join("protoc");
|
|
download_latest_protoc(&client, &protoc_zip).await?;
|
|
unzip_file(&protoc_zip, &protoc_dir)?;
|
|
std::fs::remove_file(&protoc_zip).ok();
|
|
add_bin_to_path(&protoc_dir.join("bin"))?;
|
|
println!("Protoc installed");
|
|
|
|
// 3. Download and install Git
|
|
let git_installer = deps_dir.join("git-installer.exe");
|
|
download_file(&client, GIT_URL, &git_installer).await?;
|
|
run_installer(&git_installer, true)?; // silent
|
|
println!("Git installed");
|
|
std::fs::remove_file(&git_installer).ok();
|
|
|
|
// 4. Clone wicked-waifus-rs repo recursively
|
|
let repo_dir = deps_dir.join("wicked-waifus-rs");
|
|
if !repo_dir.exists() {
|
|
let status = Command::new("git")
|
|
.arg("clone")
|
|
.arg("--recursive")
|
|
.arg(WICKED_WAIFUS_REPO)
|
|
.arg(&repo_dir)
|
|
.stdout(Stdio::null())
|
|
.stderr(Stdio::null())
|
|
.status()
|
|
.map_err(|e| format!("Failed to run git clone: {e}"))?;
|
|
if !status.success() {
|
|
return Err("git clone failed".into());
|
|
}
|
|
println!("Wicked Waifus repo cloned");
|
|
}
|
|
|
|
// 5. Build all required binaries in sequence
|
|
let bins = [
|
|
"wicked-waifus-config-server",
|
|
"wicked-waifus-login-server",
|
|
"wicked-waifus-gateway-server",
|
|
"wicked-waifus-game-server",
|
|
];
|
|
for bin in &bins {
|
|
let status = Command::new("cargo")
|
|
.arg("build")
|
|
.arg("--bin")
|
|
.arg(bin)
|
|
.current_dir(&repo_dir)
|
|
.stdout(Stdio::null())
|
|
.stderr(Stdio::null())
|
|
.status()
|
|
.map_err(|e| format!("Failed to run cargo build for {bin}: {e}"))?;
|
|
if !status.success() {
|
|
return Err(format!("cargo build failed for {bin}"));
|
|
}
|
|
println!("{bin} built");
|
|
}
|
|
|
|
println!("All binaries built");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn download_file(client: &Client, url: &str, dest: &Path) -> Result<(), String> {
|
|
let resp = client.get(url)
|
|
.header("User-Agent", "rooms-launcher")
|
|
.send().await
|
|
.map_err(|e| format!("Failed to download {url}: {e}"))?;
|
|
let bytes = resp.bytes().await.map_err(|e| format!("Failed to read bytes: {e}"))?;
|
|
let mut file = fs::File::create(dest).map_err(|e| format!("Failed to create file: {e}"))?;
|
|
file.write_all(&bytes).map_err(|e| format!("Failed to write file: {e}"))?;
|
|
println!("Downloaded {url} to {}", dest.to_string_lossy());
|
|
Ok(())
|
|
}
|
|
|
|
fn run_installer(installer: &Path, silent: bool) -> Result<(), String> {
|
|
let mut cmd = Command::new(installer);
|
|
if silent {
|
|
cmd.arg("/VERYSILENT");
|
|
}
|
|
let mut child = cmd.spawn().map_err(|e| format!("Failed to run installer: {e}"))?;
|
|
let status = child.wait().map_err(|e| format!("Failed to wait for installer: {e}"))?;
|
|
if !status.success() {
|
|
return Err(format!("Installer failed: {installer:?}"));
|
|
}
|
|
println!("{installer:?} ran successfully");
|
|
Ok(())
|
|
}
|
|
|
|
async fn download_latest_protoc(client: &Client, dest: &Path) -> Result<(), String> {
|
|
let resp = client.get(PROTOC_RELEASES_API)
|
|
.header("User-Agent", "rooms-launcher")
|
|
.send().await
|
|
.map_err(|e| format!("Failed to fetch Protoc releases: {e}"))?;
|
|
let json: serde_json::Value = resp.json().await.map_err(|e| format!("Failed to parse Protoc release JSON: {e}"))?;
|
|
let assets = json["assets"].as_array().ok_or("No assets in Protoc release JSON")?;
|
|
let asset = assets.iter().find(|a| a["name"].as_str().is_some_and(|n| n.contains(PROTOC_ASSET_KEYWORD)))
|
|
.ok_or("No zip asset found for Protoc")?;
|
|
let url = asset["browser_download_url"].as_str().ok_or("No download URL for Protoc asset")?;
|
|
download_file(client, url, dest).await?;
|
|
println!("Downloaded Protoc to {}", dest.to_string_lossy());
|
|
Ok(())
|
|
}
|
|
|
|
fn unzip_file(zip_path: &Path, dest_dir: &Path) -> Result<(), String> {
|
|
let file = fs::File::open(zip_path).map_err(|e| format!("Failed to open zip: {e}"))?;
|
|
let mut archive = zip::ZipArchive::new(file).map_err(|e| format!("Failed to read zip: {e}"))?;
|
|
fs::create_dir_all(dest_dir).map_err(|e| format!("Failed to create unzip dir: {e}"))?;
|
|
for i in 0..archive.len() {
|
|
let mut file = archive.by_index(i).map_err(|e| format!("Failed to get file in zip: {e}"))?;
|
|
let outpath = dest_dir.join(file.name());
|
|
if file.is_dir() {
|
|
fs::create_dir_all(&outpath).map_err(|e| format!("Failed to create dir in zip: {e}"))?;
|
|
} else {
|
|
if let Some(p) = outpath.parent() {
|
|
fs::create_dir_all(p).map_err(|e| format!("Failed to create parent dir: {e}"))?;
|
|
}
|
|
let mut outfile = fs::File::create(&outpath).map_err(|e| format!("Failed to create file in zip: {e}"))?;
|
|
std::io::copy(&mut file, &mut outfile).map_err(|e| format!("Failed to extract file: {e}"))?;
|
|
}
|
|
}
|
|
println!("Unzipped {} to {}", zip_path.to_string_lossy(), dest_dir.to_string_lossy());
|
|
Ok(())
|
|
}
|
|
|
|
fn add_bin_to_path(bin_dir: &Path) -> Result<(), String> {
|
|
let bin_str = bin_dir.to_str().ok_or("Invalid bin path")?;
|
|
let path = env::var("Path").unwrap_or_default();
|
|
let new_path = format!("{bin_str};{path}");
|
|
unsafe {
|
|
env::set_var("Path", &new_path);
|
|
}
|
|
println!("Added {} to Path", bin_dir.to_string_lossy());
|
|
Ok(())
|
|
} |