use std::ffi::OsString;
use std::io;
use std::path::{Path, PathBuf};
use std::process::{Child, Command, Stdio};
pub struct LaunchSpec {
pub exe: PathBuf,
pub working_dir: PathBuf,
pub args: Vec<OsString>,
}
impl LaunchSpec {
pub fn detail(&self) -> String {
let args: Vec<String> = self
.args
.iter()
.map(|a| a.to_string_lossy().into_owned())
.collect();
format!("{} {}", self.exe.display(), args.join(" "))
}
}
pub struct GameProcess(Child);
impl GameProcess {
pub fn exit_code(&mut self) -> Option<i32> {
self.0
.try_wait()
.ok()
.flatten()
.map(|status| status.code().unwrap_or(0))
}
pub fn stop(&mut self) {
let _ = self.0.kill();
let _ = self.0.wait();
}
}
pub fn spawn(spec: &LaunchSpec) -> io::Result<GameProcess> {
let mut command = Command::new(&spec.exe);
command
.current_dir(&spec.working_dir)
.args(&spec.args)
.env("SteamAppId", "236850")
.env("SteamGameId", "236850")
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null());
Ok(GameProcess(command.spawn()?))
}
pub fn link_dir(target: &Path, link: &Path) -> io::Result<()> {
#[cfg(windows)]
{
let status = Command::new("cmd")
.args(["/c", "mklink", "/J"])
.arg(link)
.arg(target)
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()?;
if status.success() {
Ok(())
} else {
Err(io::Error::other(format!(
"failed to create junction {} -> {}",
link.display(),
target.display()
)))
}
}
#[cfg(not(windows))]
{
std::os::unix::fs::symlink(target, link)
}
}
pub fn remove_layer(directory: &Path) {
let Ok(entries) = std::fs::read_dir(directory) else {
return;
};
for entry in entries.flatten() {
let Ok(file_type) = entry.file_type() else {
continue;
};
if file_type.is_symlink() || (file_type.is_dir() && is_reparse_point(&entry.path())) {
let _ = std::fs::remove_dir(entry.path());
} else if file_type.is_dir() {
let _ = std::fs::remove_dir_all(entry.path());
} else {
let _ = std::fs::remove_file(entry.path());
}
}
let _ = std::fs::remove_dir(directory);
}
#[cfg(windows)]
fn is_reparse_point(path: &Path) -> bool {
use std::os::windows::fs::MetadataExt;
const FILE_ATTRIBUTE_REPARSE_POINT: u32 = 0x400;
std::fs::symlink_metadata(path)
.map(|meta| meta.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT != 0)
.unwrap_or(false)
}
#[cfg(not(windows))]
fn is_reparse_point(_path: &Path) -> bool {
false
}