use std::io::Write;
use std::path::Path;
use std::process::Command;
use rootcause::prelude::*;
use crate::manifest::GameVersionEntry;
const APP_ID: u32 = 552990;
pub fn download_build(
build: u32,
entry: Option<&GameVersionEntry>,
data_dir: &Path,
repo_root: &Path,
username_override: Option<&str>,
) -> Result<(), Report> {
let dd_cmd = find_depot_downloader()?;
let username = resolve_username(username_override, repo_root)?;
let output_dir = data_dir.join("builds").join(build.to_string());
std::fs::create_dir_all(&output_dir).attach_with(|| format!("Failed to create {}", output_dir.display()))?;
let filelist = write_filelist(data_dir)?;
let mut cmd = Command::new(&dd_cmd);
cmd.arg("-app").arg(APP_ID.to_string());
if let Some(entry) = entry {
cmd.arg("-depot").arg(entry.depot_id.to_string());
cmd.arg("-manifest").arg(&entry.manifest_id);
}
cmd.arg("-dir").arg(&output_dir);
cmd.arg("-filelist").arg(&filelist);
cmd.arg("-username").arg(&username);
cmd.arg("-remember-password");
println!("Downloading build {build} to {}", output_dir.display());
if let Some(entry) = entry {
println!(" depot: {}, manifest: {}", entry.depot_id, entry.manifest_id);
} else {
println!(" (latest public branch)");
}
println!();
let status = cmd.status().attach_with(|| "Failed to run DepotDownloader")?;
let _ = std::fs::remove_file(&filelist);
if !status.success() {
bail!("DepotDownloader exited with status {status}");
}
println!("Download complete.");
Ok(())
}
fn find_depot_downloader() -> Result<String, Report> {
for name in &["DepotDownloader", "depotdownloader"] {
if Command::new(name).arg("--help").output().is_ok() {
return Ok(name.to_string());
}
}
if let Ok(output) = Command::new("dotnet").args(["tool", "list", "-g"]).output() {
let stdout = String::from_utf8_lossy(&output.stdout);
if stdout.to_lowercase().contains("depotdownloader") {
bail!(
"DepotDownloader is installed as a dotnet tool but not on PATH.\n\
Try running: dotnet tool install -g DepotDownloader\n\
Then ensure ~/.dotnet/tools is in your PATH."
);
}
}
bail!(
"DepotDownloader not found.\n\
Install it with: dotnet tool install -g DepotDownloader"
);
}
fn resolve_username(override_username: Option<&str>, repo_root: &Path) -> Result<String, Report> {
if let Some(u) = override_username {
return Ok(u.to_string());
}
let steam_user_file = repo_root.join(".steam-user");
if steam_user_file.exists() {
let user =
std::fs::read_to_string(&steam_user_file).attach_with(|| "Failed to read .steam-user")?.trim().to_string();
if !user.is_empty() {
println!("Using saved Steam username: {user}");
println!("(delete .steam-user to change)");
return Ok(user);
}
}
println!("World of Warships requires a Steam account to download.");
println!("Your username will be saved to .steam-user for future runs.");
println!();
print!("Steam username: ");
std::io::stdout().flush()?;
let mut input = String::new();
std::io::stdin().read_line(&mut input)?;
let username = input.trim().to_string();
if username.is_empty() {
bail!("No username provided");
}
std::fs::write(&steam_user_file, &username).attach_with(|| "Failed to save .steam-user")?;
Ok(username)
}
fn write_filelist(data_dir: &Path) -> Result<std::path::PathBuf, Report> {
let filelist_path = data_dir.join(".filelist.tmp");
let content = "regex:bin/\\d+/idx/.*\\.idx$\nregex:res_packages/.*\\.pkg$\n";
std::fs::create_dir_all(data_dir)?;
std::fs::write(&filelist_path, content)
.attach_with(|| format!("Failed to write filelist to {}", filelist_path.display()))?;
Ok(filelist_path)
}