use super::{BsaArchive, BsaError};
use std::path::Path;
const OFFICIAL_MASTER_FILES: &[&str] = &[
"skyrim",
"update",
"dawnguard",
"dragonborn",
"hearthfires",
];
pub struct BsaStringsProvider {
archive: BsaArchive,
}
impl BsaStringsProvider {
pub fn open_for_plugin<P: AsRef<Path>>(plugin_path: P) -> Result<Self, BsaError> {
let plugin_path = plugin_path.as_ref();
let plugin_dir = plugin_path
.parent()
.ok_or_else(|| BsaError::Io(std::io::Error::new(
std::io::ErrorKind::NotFound,
"无法获取插件目录",
)))?;
let plugin_name = plugin_path
.file_stem()
.and_then(|s| s.to_str())
.ok_or_else(|| BsaError::Io(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"无法获取插件名称",
)))?;
let bsa_path = if Self::is_official_master(plugin_name) {
plugin_dir.join("Skyrim - Interface.bsa")
} else {
plugin_dir.join(format!("{}.bsa", plugin_name))
};
if !bsa_path.exists() {
return Err(BsaError::Io(std::io::Error::new(
std::io::ErrorKind::NotFound,
format!("BSA 文件不存在: {}", bsa_path.display()),
)));
}
let archive = BsaArchive::open(bsa_path)?;
Ok(Self { archive })
}
pub fn extract_strings(
&self,
plugin_name: &str,
language: &str,
extension: &str,
) -> Result<Vec<u8>, BsaError> {
let filename = format!("{}_{}.{}", plugin_name, language, extension);
let path_variants = vec![
format!("strings/{}", filename.to_lowercase()), format!("Strings/{}", filename), format!("strings/{}", filename), format!("Strings/{}", filename.to_lowercase()), ];
for path in &path_variants {
match self.archive.extract(path) {
Ok(data) => return Ok(data),
Err(BsaError::NotFound(_)) => continue, Err(e) => return Err(e), }
}
Err(BsaError::NotFound(format!(
"在 BSA 中找不到 strings 文件: {} (尝试了 {} 个路径变体)",
filename,
path_variants.len()
)))
}
pub fn list_strings_files(&self) -> Vec<String> {
self.archive
.file_list()
.into_iter()
.filter(|path| {
let lower = path.to_lowercase();
lower.ends_with(".strings")
|| lower.ends_with(".ilstrings")
|| lower.ends_with(".dlstrings")
})
.collect()
}
fn is_official_master(plugin_name: &str) -> bool {
let lower = plugin_name.to_lowercase();
OFFICIAL_MASTER_FILES.contains(&lower.as_str())
}
pub fn archive(&self) -> &BsaArchive {
&self.archive
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_official_master() {
assert!(BsaStringsProvider::is_official_master("Skyrim"));
assert!(BsaStringsProvider::is_official_master("skyrim"));
assert!(BsaStringsProvider::is_official_master("SKYRIM"));
assert!(BsaStringsProvider::is_official_master("Update"));
assert!(BsaStringsProvider::is_official_master("Dawnguard"));
assert!(BsaStringsProvider::is_official_master("Dragonborn"));
assert!(BsaStringsProvider::is_official_master("HearthFires"));
assert!(!BsaStringsProvider::is_official_master("MyMod"));
assert!(!BsaStringsProvider::is_official_master("CustomContent"));
}
}