use super::Plugin;
use crate::datatypes::{read_u32, RawString};
use crate::record::Record;
use crate::group::Group;
use crate::string_file::StringFileSet;
use crate::string_routes::DefaultStringRouter;
use crate::io::EspReader;
use crate::utils::EspError;
use std::collections::HashMap;
use std::path::PathBuf;
use std::io::{Cursor, Read};
use std::sync::Arc;
use serde_json;
use memmap2::Mmap;
use rayon::prelude::*;
impl Plugin {
pub fn load_with_reader(
path: PathBuf,
reader: &dyn EspReader,
) -> Result<Self, Box<dyn std::error::Error>> {
let string_records = Self::load_string_records()?;
#[allow(deprecated)]
let string_router = Arc::new(DefaultStringRouter::new(string_records.clone()));
let raw_data = reader.read(&path)?;
let data_bytes = raw_data.bytes;
let mmap: Option<Arc<Mmap>> = None;
let mut cursor = Cursor::new(&data_bytes[..]);
let header = Record::parse(&mut cursor)?;
Self::validate_esp_file(&header)?;
let masters = Self::extract_masters(&header);
let groups = Self::parse_groups(&mut cursor, &data_bytes[..])?;
#[allow(deprecated)]
Ok(Plugin {
path,
header,
groups,
masters,
string_records,
string_router,
string_files: None,
language: String::new(),
mmap,
})
}
pub fn load(path: PathBuf) -> Result<Self, Box<dyn std::error::Error>> {
let string_records = Self::load_string_records()?;
#[allow(deprecated)]
let string_router = Arc::new(DefaultStringRouter::new(string_records.clone()));
let file = std::fs::File::open(&path)?;
let mmap = unsafe { Mmap::map(&file)? };
let mmap = Arc::new(mmap);
let mut cursor = Cursor::new(&mmap[..]);
let header = Record::parse(&mut cursor)?;
Self::validate_esp_file(&header)?;
let masters = Self::extract_masters(&header);
let groups = Self::parse_groups(&mut cursor, &mmap[..])?;
#[allow(deprecated)]
Ok(Plugin {
path,
header,
groups,
masters,
string_records,
string_router,
string_files: None,
language: String::new(),
mmap: Some(mmap),
})
}
#[deprecated(
since = "0.4.0",
note = "使用 Plugin::load() 代替。如需加载 STRING 文件,请使用 LocalizedPluginContext::load()"
)]
pub fn new(path: PathBuf, language: Option<&str>) -> Result<Self, Box<dyn std::error::Error>> {
let language = language.unwrap_or("english").to_string();
let string_records = Self::load_string_records()?;
let string_router = Arc::new(DefaultStringRouter::new(string_records.clone()));
let file = std::fs::File::open(&path)?;
let mmap = unsafe { Mmap::map(&file)? };
let mmap = Arc::new(mmap);
let mut cursor = Cursor::new(&mmap[..]);
let header = Record::parse(&mut cursor)?;
Self::validate_esp_file(&header)?;
let masters = Self::extract_masters(&header);
let groups = Self::parse_groups(&mut cursor, &mmap[..])?;
let is_localized = header.flags & 0x00000080 != 0;
let string_files = if is_localized {
let plugin_dir = path.parent().ok_or("无法获取插件目录")?;
let plugin_name = path.file_stem()
.and_then(|s| s.to_str())
.ok_or("无法获取插件名称")?;
let search_dirs = vec![
plugin_dir.to_path_buf(), plugin_dir.join("Strings"), plugin_dir.join("strings"), ];
let mut loaded_set: Option<StringFileSet> = None;
for dir in search_dirs {
if !dir.exists() {
continue;
}
match StringFileSet::load_from_directory(&dir, plugin_name, &language) {
Ok(set) if !set.files.is_empty() => {
#[cfg(debug_assertions)]
println!("已加载STRING文件: {} 个文件类型(从 {:?})", set.files.len(), dir);
loaded_set = Some(set);
break;
}
Ok(_) => {
#[cfg(debug_assertions)]
eprintln!("提示: {:?} 目录下未找到STRING文件", dir);
}
Err(_e) => {
#[cfg(debug_assertions)]
eprintln!("警告: 无法从 {:?} 加载STRING文件: {}", dir, _e);
}
}
}
if loaded_set.is_none() {
#[cfg(debug_assertions)]
eprintln!("警告: 本地化插件但未找到任何STRING文件");
}
loaded_set
} else {
None
};
Ok(Plugin {
path,
header,
groups,
masters,
string_records,
string_router,
string_files,
language,
mmap: Some(mmap),
})
}
pub(crate) fn validate_esp_file(header: &Record) -> Result<(), Box<dyn std::error::Error>> {
if !matches!(header.record_type.as_str(), "TES4" | "TES3") {
return Err(EspError::InvalidFormat.into());
}
Ok(())
}
pub(crate) fn parse_groups(cursor: &mut Cursor<&[u8]>, data: &[u8]) -> Result<Vec<Group>, Box<dyn std::error::Error>> {
let group_ranges = Self::scan_group_boundaries(cursor, data)?;
if group_ranges.is_empty() {
return Ok(Vec::new());
}
let groups: Result<Vec<Group>, String> = group_ranges
.par_iter()
.map(|&(start, size)| -> Result<Group, String> {
let end = start + size as u64;
if end > data.len() as u64 {
return Err(format!("Group 边界超出数据范围: {}..{} (数据长度: {})", start, end, data.len()));
}
let group_data = &data[start as usize..end as usize];
let mut group_cursor = Cursor::new(group_data);
Group::parse(&mut group_cursor).map_err(|e| e.to_string())
})
.collect();
groups.map_err(|e| e.into())
}
fn scan_group_boundaries(cursor: &mut Cursor<&[u8]>, data: &[u8]) -> Result<Vec<(u64, u32)>, Box<dyn std::error::Error>> {
let mut boundaries = Vec::new();
let start_pos = cursor.position();
while cursor.position() < data.len() as u64 {
let pos = cursor.position();
if pos + 8 > data.len() as u64 {
break;
}
let mut type_bytes = [0u8; 4];
if cursor.read_exact(&mut type_bytes).is_err() {
break;
}
if &type_bytes != b"GRUP" {
return Err(format!("在位置 {} 期望 GRUP,但找到 {}",
pos, String::from_utf8_lossy(&type_bytes)).into());
}
let size = read_u32(cursor)?;
if size < 24 || size > 200_000_000 {
return Err(format!("在位置 {} 发现异常 Group 大小: {} bytes", pos, size).into());
}
boundaries.push((pos, size));
cursor.set_position(pos + size as u64);
}
cursor.set_position(start_pos);
Ok(boundaries)
}
pub(crate) fn load_string_records() -> Result<HashMap<String, Vec<String>>, Box<dyn std::error::Error>> {
let json_data = include_str!("../../data/string_records.json");
Ok(serde_json::from_str(json_data)?)
}
pub(crate) fn extract_masters(header: &Record) -> Vec<String> {
header.subrecords.iter()
.filter(|sr| sr.record_type == "MAST")
.map(|sr| RawString::parse_zstring(&sr.data).content)
.collect()
}
}