mirror of
https://codeberg.org/icewind/tf-asset-loader.git
synced 2026-06-03 16:44:10 +02:00
initial version
This commit is contained in:
commit
9fd30d00ca
9 changed files with 1568 additions and 0 deletions
111
src/lib.rs
Normal file
111
src/lib.rs
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
pub mod source;
|
||||
|
||||
pub use source::AssetSource;
|
||||
use std::env::var_os;
|
||||
use std::fmt::Debug;
|
||||
use std::path::PathBuf;
|
||||
use steamlocate::SteamDir;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum LoaderError {
|
||||
#[error("Failed to find tf2 install location")]
|
||||
Tf2NotFound,
|
||||
#[error(transparent)]
|
||||
Io(#[from] std::io::Error),
|
||||
#[cfg(feature = "zip-lzma")]
|
||||
#[error(transparent)]
|
||||
Zip(#[from] zip_lzma::result::ZipError),
|
||||
#[error("{0}")]
|
||||
Other(String),
|
||||
}
|
||||
|
||||
pub struct Loader {
|
||||
sources: Vec<Box<dyn AssetSource>>,
|
||||
}
|
||||
|
||||
impl Loader {
|
||||
/// Create the loader
|
||||
pub fn new() -> Result<Self, LoaderError> {
|
||||
let tf2_dir = tf2_path()?;
|
||||
|
||||
let tf_dir = tf2_dir.join("tf");
|
||||
let hl_dir = tf2_dir.join("hl2");
|
||||
let download = tf_dir.join("download");
|
||||
|
||||
#[cfg(feature = "bsp")]
|
||||
let vpks = tf_dir
|
||||
.read_dir()?
|
||||
.chain(hl_dir.read_dir()?)
|
||||
.filter_map(|item| item.ok())
|
||||
.filter_map(|item| Some(item.path().to_str()?.to_string()))
|
||||
.filter(|path| path.ends_with("dir.vpk"))
|
||||
.map(|path| vpk::from_path(&path))
|
||||
.filter_map(|res| res.ok())
|
||||
.map(|vpk| Box::new(vpk) as Box<dyn AssetSource>);
|
||||
|
||||
#[allow(unused_mut)]
|
||||
let mut sources = vec![
|
||||
Box::new(tf_dir) as Box<dyn AssetSource>,
|
||||
Box::new(download),
|
||||
Box::new(hl_dir),
|
||||
];
|
||||
|
||||
#[cfg(feature = "bsp")]
|
||||
sources.extend(vpks);
|
||||
|
||||
Ok(Loader { sources })
|
||||
}
|
||||
|
||||
pub fn add_source<S: AssetSource + 'static>(&mut self, source: S) {
|
||||
self.sources.push(Box::new(source))
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn exists(&self, name: &str) -> Result<bool, LoaderError> {
|
||||
for source in self.sources.iter() {
|
||||
if source.has(name)? {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn load(&self, name: &str) -> Result<Option<Vec<u8>>, LoaderError> {
|
||||
for source in self.sources.iter() {
|
||||
if let Some(data) = source.load(name)? {
|
||||
return Ok(Some(data));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub fn find_in_paths(&self, name: &str, paths: &[String]) -> Option<String> {
|
||||
for path in paths {
|
||||
let full_path = format!("{}{}", path, name);
|
||||
if self.exists(&full_path).unwrap_or_default() {
|
||||
return Some(full_path);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn tf2_path() -> Result<PathBuf, LoaderError> {
|
||||
if let Some(path) = var_os("TF_DIR") {
|
||||
let path: PathBuf = path.into();
|
||||
if path.is_dir() {
|
||||
Ok(path)
|
||||
} else {
|
||||
Err(LoaderError::Tf2NotFound)
|
||||
}
|
||||
} else {
|
||||
Ok(SteamDir::locate()
|
||||
.ok_or(LoaderError::Tf2NotFound)?
|
||||
.app(&440)
|
||||
.ok_or(LoaderError::Tf2NotFound)?
|
||||
.path
|
||||
.clone())
|
||||
}
|
||||
}
|
||||
70
src/source.rs
Normal file
70
src/source.rs
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
use crate::LoaderError;
|
||||
use std::fs::read;
|
||||
use std::io::ErrorKind;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub trait AssetSource {
|
||||
fn has(&self, path: &str) -> Result<bool, LoaderError>;
|
||||
|
||||
fn load(&self, path: &str) -> Result<Option<Vec<u8>>, LoaderError>;
|
||||
}
|
||||
|
||||
impl AssetSource for PathBuf {
|
||||
fn has(&self, path: &str) -> Result<bool, LoaderError> {
|
||||
Ok(self.join(path).exists())
|
||||
}
|
||||
|
||||
fn load(&self, path: &str) -> Result<Option<Vec<u8>>, LoaderError> {
|
||||
match read(self.join(path)) {
|
||||
Ok(data) => Ok(Some(data)),
|
||||
Err(e) if e.kind() == ErrorKind::NotFound => Ok(None),
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "vpk")]
|
||||
mod vdf {
|
||||
use super::AssetSource;
|
||||
use crate::LoaderError;
|
||||
use vpk::VPK;
|
||||
|
||||
impl AssetSource for VPK {
|
||||
fn has(&self, path: &str) -> Result<bool, LoaderError> {
|
||||
Ok(self.tree.contains_key(path))
|
||||
}
|
||||
|
||||
fn load(&self, path: &str) -> Result<Option<Vec<u8>>, LoaderError> {
|
||||
if let Some(entry) = self.tree.get(path) {
|
||||
Ok(Some(entry.get()?.into()))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "vbsp")]
|
||||
mod vbsp {
|
||||
use super::AssetSource;
|
||||
use crate::LoaderError;
|
||||
use vbsp::{BspError, Packfile};
|
||||
|
||||
impl AssetSource for Packfile {
|
||||
fn has(&self, path: &str) -> Result<bool, LoaderError> {
|
||||
match self.has(path) {
|
||||
Ok(found) => Ok(found),
|
||||
Err(BspError::Zip(err)) => Err(err.into()),
|
||||
Err(e) => Err(LoaderError::Other(e.to_string())), // the error *should* always be a zip error
|
||||
}
|
||||
}
|
||||
|
||||
fn load(&self, path: &str) -> Result<Option<Vec<u8>>, LoaderError> {
|
||||
match self.get(path) {
|
||||
Ok(data) => Ok(data),
|
||||
Err(BspError::Zip(err)) => Err(err.into()),
|
||||
Err(e) => Err(LoaderError::Other(e.to_string())), // the error *should* always be a zip error
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue