use crate::data::decompress;
use crate::Result;
use std::path::Path;
pub struct DirectoryReader {
path: std::path::PathBuf,
files: Vec<String>,
}
impl DirectoryReader {
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
let path = path.as_ref().to_path_buf();
let mut files = Vec::new();
for entry in walkdir::WalkDir::new(&path)
.into_iter()
.filter_map(|e| e.ok())
{
let entry_path = entry.path();
if entry_path.is_file() {
if let Some(ext) = entry_path.extension() {
if ext == "ncs" || ext == "bin" {
if let Some(name) = entry_path.file_name() {
files.push(name.to_string_lossy().to_string());
}
}
}
}
}
Ok(Self { path, files })
}
pub fn list_files(&self) -> &[String] {
&self.files
}
pub fn read(&self, filename: &str) -> Result<Vec<u8>> {
let direct = self.path.join(filename);
if direct.exists() {
return std::fs::read(&direct).map_err(crate::Error::Io);
}
for entry in walkdir::WalkDir::new(&self.path)
.into_iter()
.filter_map(|e| e.ok())
{
let entry_path = entry.path();
if entry_path.is_file() {
if let Some(name) = entry_path.file_name() {
if name.to_string_lossy() == filename {
return std::fs::read(entry_path).map_err(crate::Error::Io);
}
}
}
}
Err(crate::Error::Io(std::io::Error::new(
std::io::ErrorKind::NotFound,
format!("File not found: {}", filename),
)))
}
pub fn read_decompressed(&self, filename: &str) -> Result<Vec<u8>> {
let raw = self.read(filename)?;
decompress(&raw)
}
}
pub fn type_from_filename(filename: &str) -> String {
let name = filename.rsplit('/').next().unwrap_or(filename);
let name = name.rsplit('\\').next().unwrap_or(name);
name.strip_prefix("Nexus-Data-")
.and_then(|s| {
s.rfind(|c: char| c.is_ascii_digit()).map(|pos| &s[..pos])
})
.unwrap_or(name)
.to_string()
}
pub fn is_ncs_file(filename: &str) -> bool {
filename.to_lowercase().ends_with(".ncs")
}
#[derive(Debug, Clone)]
pub struct ExtractedNcs {
pub filename: String,
pub type_name: String,
pub raw_data: Vec<u8>,
}
impl ExtractedNcs {
pub fn new(filename: String, raw_data: Vec<u8>) -> Self {
let type_name = type_from_filename(&filename);
Self {
filename,
type_name,
raw_data,
}
}
pub fn decompress(&self) -> Result<Vec<u8>> {
decompress(&self.raw_data)
}
}
pub fn extract_from_directory<P: AsRef<Path>>(path: P) -> Result<Vec<ExtractedNcs>> {
let reader = DirectoryReader::open(path)?;
let mut results = Vec::with_capacity(reader.files.len());
for filename in &reader.files {
let raw_data = reader.read(filename)?;
results.push(ExtractedNcs::new(filename.clone(), raw_data));
}
Ok(results)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_type_from_filename() {
assert_eq!(type_from_filename("Nexus-Data-itempool0.ncs"), "itempool");
assert_eq!(
type_from_filename("Nexus-Data-achievement6.ncs"),
"achievement"
);
assert_eq!(
type_from_filename("Nexus-Data-aim_assist_parameters0.ncs"),
"aim_assist_parameters"
);
assert_eq!(
type_from_filename("Engine/Content/_NCS/Nexus-Data-itempool0.ncs"),
"itempool"
);
assert_eq!(
type_from_filename("Engine\\Content\\_NCS\\Nexus-Data-itempool0.ncs"),
"itempool"
);
assert_eq!(type_from_filename("custom.ncs"), "custom.ncs");
}
#[test]
fn test_is_ncs_file() {
assert!(is_ncs_file("test.ncs"));
assert!(is_ncs_file("Nexus-Data-itempool0.ncs"));
assert!(is_ncs_file("path/to/file.NCS"));
assert!(!is_ncs_file("test.bin"));
assert!(!is_ncs_file("test.txt"));
}
}