mirror of
https://codeberg.org/icewind/tf-asset-loader.git
synced 2026-06-03 16:44:10 +02:00
improve path searching
This commit is contained in:
parent
e38ce5b316
commit
15f49fabfb
3 changed files with 76 additions and 8 deletions
12
Cargo.lock
generated
12
Cargo.lock
generated
|
|
@ -521,6 +521,15 @@ dependencies = [
|
||||||
"syn 2.0.76",
|
"syn 2.0.76",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "path-dedot"
|
||||||
|
version = "3.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "07ba0ad7e047712414213ff67533e6dd477af0a4e1d14fb52343e53d30ea9397"
|
||||||
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pest"
|
name = "pest"
|
||||||
version = "2.7.5"
|
version = "2.7.5"
|
||||||
|
|
@ -747,8 +756,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tf-asset-loader"
|
name = "tf-asset-loader"
|
||||||
version = "0.1.6"
|
version = "0.1.7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"path-dedot",
|
||||||
"steamlocate",
|
"steamlocate",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "tf-asset-loader"
|
name = "tf-asset-loader"
|
||||||
version = "0.1.6"
|
version = "0.1.7"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
description = "Utility for loading assets from tf2 data files"
|
description = "Utility for loading assets from tf2 data files"
|
||||||
|
|
@ -14,6 +14,7 @@ vpk = { version = "0.2.0", optional = true }
|
||||||
vbsp = { version = "0.6.0", optional = true }
|
vbsp = { version = "0.6.0", optional = true }
|
||||||
thiserror = "1.0.63"
|
thiserror = "1.0.63"
|
||||||
zip = { package = "zip-lzma", version = "0.6.3", default-features = false, features = ["lzma"], optional = true }
|
zip = { package = "zip-lzma", version = "0.6.3", default-features = false, features = ["lzma"], optional = true }
|
||||||
|
path-dedot = "3.1.1"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
bsp = ["vbsp", "zip"]
|
bsp = ["vbsp", "zip"]
|
||||||
|
|
|
||||||
69
src/lib.rs
69
src/lib.rs
|
|
@ -5,7 +5,7 @@
|
||||||
//! directory.
|
//! directory.
|
||||||
//!
|
//!
|
||||||
//! Supports loading both plain file data and data embedded in `vpk` files.
|
//! Supports loading both plain file data and data embedded in `vpk` files.
|
||||||
//! ```rust,dont-run
|
//! ```rust,no_run
|
||||||
//! # use tf_asset_loader::{Loader, LoaderError};
|
//! # use tf_asset_loader::{Loader, LoaderError};
|
||||||
//! #
|
//! #
|
||||||
//! fn main() -> Result<(), LoaderError> {
|
//! fn main() -> Result<(), LoaderError> {
|
||||||
|
|
@ -19,9 +19,11 @@
|
||||||
|
|
||||||
pub mod source;
|
pub mod source;
|
||||||
|
|
||||||
|
use path_dedot::ParseDot;
|
||||||
pub use source::AssetSource;
|
pub use source::AssetSource;
|
||||||
|
use std::borrow::Cow;
|
||||||
use std::env::var_os;
|
use std::env::var_os;
|
||||||
use std::fmt::{Debug, Formatter};
|
use std::fmt::{Debug, Display, Formatter};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use steamlocate::SteamDir;
|
use steamlocate::SteamDir;
|
||||||
|
|
@ -119,11 +121,22 @@ impl Loader {
|
||||||
/// Check if a file by path exists.
|
/// Check if a file by path exists.
|
||||||
#[tracing::instrument(skip(self))]
|
#[tracing::instrument(skip(self))]
|
||||||
pub fn exists(&self, name: &str) -> Result<bool, LoaderError> {
|
pub fn exists(&self, name: &str) -> Result<bool, LoaderError> {
|
||||||
|
let name = clean_path(name);
|
||||||
for source in self.sources.iter() {
|
for source in self.sources.iter() {
|
||||||
if source.has(name)? {
|
if source.has(&name)? {
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let lower_name = name.to_ascii_lowercase();
|
||||||
|
if name != lower_name {
|
||||||
|
for source in self.sources.iter() {
|
||||||
|
if source.has(&lower_name)? {
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(false)
|
Ok(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -132,26 +145,70 @@ impl Loader {
|
||||||
/// Returns the file data as `Vec<u8>` or `None` if the path doesn't exist.
|
/// Returns the file data as `Vec<u8>` or `None` if the path doesn't exist.
|
||||||
#[tracing::instrument(skip(self))]
|
#[tracing::instrument(skip(self))]
|
||||||
pub fn load(&self, name: &str) -> Result<Option<Vec<u8>>, LoaderError> {
|
pub fn load(&self, name: &str) -> Result<Option<Vec<u8>>, LoaderError> {
|
||||||
|
let name = clean_path(name);
|
||||||
for source in self.sources.iter() {
|
for source in self.sources.iter() {
|
||||||
if let Some(data) = source.load(name)? {
|
if let Some(data) = source.load(&name)? {
|
||||||
return Ok(Some(data));
|
return Ok(Some(data));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let lower_name = name.to_ascii_lowercase();
|
||||||
|
if name != lower_name {
|
||||||
|
for source in self.sources.iter() {
|
||||||
|
if let Some(data) = source.load(&lower_name)? {
|
||||||
|
return Ok(Some(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Look for a file by name in one or more paths
|
/// Look for a file by name in one or more paths
|
||||||
pub fn find_in_paths(&self, name: &str, paths: &[String]) -> Option<String> {
|
pub fn find_in_paths<S: Display>(&self, name: &str, paths: &[S]) -> Option<String> {
|
||||||
for path in paths {
|
for path in paths {
|
||||||
let full_path = format!("{}{}", path, name);
|
let full_path = format!("{}{}", path, name);
|
||||||
|
let full_path = clean_path(&full_path);
|
||||||
if self.exists(&full_path).unwrap_or_default() {
|
if self.exists(&full_path).unwrap_or_default() {
|
||||||
return Some(full_path);
|
return Some(full_path.to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let lower_name = name.to_ascii_lowercase();
|
||||||
|
if name != lower_name {
|
||||||
|
for path in paths {
|
||||||
|
let full_path = format!("{}{}", path, lower_name);
|
||||||
|
let full_path = clean_path(&full_path);
|
||||||
|
if self.exists(&full_path).unwrap_or_default() {
|
||||||
|
return Some(full_path.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn clean_path(path: &str) -> Cow<str> {
|
||||||
|
if path.contains("/../") {
|
||||||
|
let path_buf = PathBuf::from(format!("/{path}"));
|
||||||
|
let Ok(absolute_path) = path_buf.parse_dot_from("/") else {
|
||||||
|
return path.into();
|
||||||
|
};
|
||||||
|
let path = absolute_path.to_str().unwrap().trim_start_matches('/');
|
||||||
|
String::from(path).into()
|
||||||
|
} else {
|
||||||
|
path.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_clean_path() {
|
||||||
|
assert_eq!("foo/bar", clean_path("foo/bar"));
|
||||||
|
assert_eq!("foo/bar", clean_path("foo/asd/../bar"));
|
||||||
|
assert_eq!("../bar", clean_path("../bar"));
|
||||||
|
}
|
||||||
|
|
||||||
fn tf2_path() -> Result<PathBuf, LoaderError> {
|
fn tf2_path() -> Result<PathBuf, LoaderError> {
|
||||||
if let Some(path) = var_os("TF_DIR") {
|
if let Some(path) = var_os("TF_DIR") {
|
||||||
let path: PathBuf = path.into();
|
let path: PathBuf = path.into();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue