use crate::pallet::vpk;
use vpk::utils::*;
use vpk::{VpkError, VpkResult};
use std::fs;
use std::path::{Path, PathBuf};
use walkdir::WalkDir;
pub fn get_search_paths(
game_root: impl AsRef<Path>, game_info: impl AsRef<Path>,
) -> Option<Vec<PathBuf>> {
fn search_paths(game_root: &Path, game_info: &Path) -> Option<Vec<PathBuf>> {
let game_info = game_info.canonicalize().ok()?;
let string = fs::read_to_string(&game_info).ok()?;
let game_info_path = game_info.parent()?;
let after_open_brace = string.split_once("SearchPaths")?.1.split_once('{')?.1;
let between_braces = after_open_brace.split_once('}')?.0;
let mut paths = Vec::new();
for line in between_braces.lines() {
let Some((_, path)) = line.split_once("Game") else { continue };
let path = path.trim().to_lowercase();
if path.contains("custom") {
continue;
}
let path = path.replace("|gameinfo_path|", &format!("{}/", game_info_path.display()));
let path = game_root.join(path);
paths.push(path);
}
Some(paths)
}
search_paths(game_root.as_ref(), game_info.as_ref())
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct Content {
pub mdl: Vec<PathBuf>,
pub vmt: Vec<PathBuf>,
}
impl Content {
pub const fn new() -> Self {
Self { mdl: Vec::new(), vmt: Vec::new() }
}
pub fn into_parts(self) -> (Vec<PathBuf>, Vec<PathBuf>) {
(self.mdl, self.vmt)
}
}
pub fn filter_no_addons(entry: &walkdir::DirEntry) -> bool {
let path = entry.path();
!(path.contains("addon")
|| path.contains("workshop")
|| path.contains("custom")
|| path.contains("download"))
}
pub fn get_content<P>(
vpk_bin: impl AsRef<Path>, game_root: impl AsRef<Path>, game_info: impl AsRef<Path>, filter: P,
) -> VpkResult<Content>
where
P: FnMut(&walkdir::DirEntry) -> bool,
{
fn get_content<P>(
vpk_bin: &Path, game_root: impl AsRef<Path>, game_info: &Path, mut filter: P,
) -> VpkResult<Content>
where
P: FnMut(&walkdir::DirEntry) -> bool,
{
let search_paths = get_search_paths(game_root, game_info)
.ok_or_else(|| {
io_error_other(format!("Error parsing game_info.txt: `{}`", game_info.display()))
})
.map_err(VpkError::Src)?;
let mut content = Content::new();
for path in search_paths {
dir_content(vpk_bin, path, &mut content, &mut filter)?;
}
Ok(content)
}
get_content(vpk_bin.as_ref(), game_root.as_ref(), game_info.as_ref(), filter)
}
pub fn dir_content<P>(
vpk_bin: impl AsRef<Path>, dir: impl AsRef<Path>, content: &mut Content, filter: P,
) -> VpkResult<()>
where
P: FnMut(&walkdir::DirEntry) -> bool,
{
fn dir_content<P>(vpk_bin: &Path, dir: &Path, content: &mut Content, filter: P) -> VpkResult<()>
where
P: FnMut(&walkdir::DirEntry) -> bool,
{
dir.dir_exists().map_err(VpkError::Src)?;
let entries =
WalkDir::new(dir).follow_links(true).into_iter().filter_map(|entry| entry.ok());
for entry in entries.filter(filter) {
let path = entry.path();
if path.contains("addon")
|| path.contains("workshop")
|| path.contains("custom")
|| path.contains("download")
{
panic!("bruh impossible");
continue;
}
match path {
mdl if path.has_ext("mdl") => {
content.mdl.push(mdl.to_path_buf());
}
vmt if path.has_ext("vmt") => {
content.vmt.push(vmt.to_path_buf());
}
vpk if path.has_ext("vpk") => {
vpk_content(vpk_bin, vpk, content)?;
}
_ => (),
}
}
Ok(())
}
dir_content(vpk_bin.as_ref(), dir.as_ref(), content, filter)
}
pub fn vpk_content(
vpk_bin: impl AsRef<Path>, vpk: impl AsRef<Path>, content: &mut Content,
) -> VpkResult<()> {
fn vpk_content(vpk_bin: &Path, vpk: &Path, content: &mut Content) -> VpkResult<()> {
vpk.vpk_exists().map_err(VpkError::Src)?;
if vpk.is_proper_vpk_ish().is_err() {
return Ok(());
}
let entries = vpk::list(vpk_bin, vpk)?;
for entry in entries.lines() {
let path = Path::new(entry);
match path {
mdl if path.has_ext("mdl") => content.mdl.push(mdl.to_path_buf()),
vmt if path.has_ext("vmt") => content.vmt.push(vmt.to_path_buf()),
#[cfg(debug_assertions)]
vpk if path.has_ext("vpk") => {
eprintln!("nested vpk in {}", vpk.display());
panic!();
}
_ => (),
}
}
Ok(())
}
vpk_content(vpk_bin.as_ref(), vpk.as_ref(), content)
}
#[cfg(test)]
mod tests {
use super::*;
#[ignore]
#[test]
fn it_works() {
let truth: &[PathBuf] = &[
"custom".into(),
"update".into(),
"left4dead2_dlc3".into(),
"left4dead2_dlc2".into(),
"left4dead2_dlc1".into(),
"/home/redram/.local/share/Steam/steamapps/common/Left 4 Dead 2/left4dead2".into(),
"hl2".into(),
];
const L4D2_ROOT: &str = "/home/redram/dev/modding/L4D2/Left 4 Dead 2/";
const L4D2_GAMEINFO: &str =
"/home/redram/dev/modding/L4D2/Left 4 Dead 2/left4dead2/gameinfo.txt";
let result = get_search_paths(L4D2_ROOT, L4D2_GAMEINFO).unwrap();
assert_eq!(truth, result);
}
}