use anyhow::{Context, Result};
use regex::Regex;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
use std::path::Path;
use crate::manifest::PakManifest;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExtractedElement {
pub internal_name: String,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub example_paths: Vec<String>,
}
pub fn extract_elements_from_pak(
pak_manifest_path: &Path,
) -> Result<HashMap<String, ExtractedElement>> {
let content =
fs::read_to_string(pak_manifest_path).context("Failed to read pak_manifest.json")?;
let manifest: PakManifest =
serde_json::from_str(&content).context("Failed to parse pak_manifest.json")?;
let mut elements: HashMap<String, ExtractedElement> = HashMap::new();
let element_pattern =
Regex::new(r"/(?:Effects|Materials)/(?:Textures|Materials)?/?Elements/([A-Za-z]+)/")
.unwrap();
for item in &manifest.items {
let path = &item.path;
if let Some(cap) = element_pattern.captures(path) {
let element_name = cap[1].to_string();
if element_name.len() < 3 {
continue;
}
let elem = elements
.entry(element_name.clone())
.or_insert_with(|| ExtractedElement {
internal_name: element_name.clone(),
example_paths: Vec::new(),
});
if elem.example_paths.len() < 3 {
elem.example_paths.push(path.clone());
}
}
}
Ok(elements)
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExtractedRarity {
pub tier: u8,
pub code: String,
pub name: String,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub example_paths: Vec<String>,
}
pub fn extract_rarities_from_pak(pak_manifest_path: &Path) -> Result<Vec<ExtractedRarity>> {
let content =
fs::read_to_string(pak_manifest_path).context("Failed to read pak_manifest.json")?;
let manifest: PakManifest =
serde_json::from_str(&content).context("Failed to parse pak_manifest.json")?;
let mut rarities: HashMap<u8, ExtractedRarity> = HashMap::new();
let rarity_pip_pattern = Regex::new(r"rarity_pip_(\d{2})_([a-z]+)").unwrap();
let comp_pattern = Regex::new(r"comp_(\d{2})_([a-z]+)").unwrap();
for item in &manifest.items {
let path = &item.path;
let path_lower = path.to_lowercase();
if let Some(cap) = rarity_pip_pattern.captures(&path_lower) {
let tier: u8 = cap[1].parse().unwrap_or(0);
let name = cap[2].to_string();
if (1..=5).contains(&tier) {
let rarity = rarities.entry(tier).or_insert_with(|| ExtractedRarity {
tier,
code: format!("comp_{:02}", tier),
name: name.clone(),
example_paths: Vec::new(),
});
if rarity.example_paths.len() < 3 {
rarity.example_paths.push(path.clone());
}
}
}
for prop in &item.property_names {
let prop_lower = prop.to_lowercase();
if let Some(cap) = comp_pattern.captures(&prop_lower) {
let tier: u8 = cap[1].parse().unwrap_or(0);
let name = cap[2].to_string();
if (1..=5).contains(&tier) {
let rarity = rarities.entry(tier).or_insert_with(|| ExtractedRarity {
tier,
code: format!("comp_{:02}", tier),
name: name.clone(),
example_paths: Vec::new(),
});
if rarity.name.is_empty() || rarity.name == "unknown" {
rarity.name = name;
}
}
}
}
}
let mut result: Vec<ExtractedRarity> = rarities.into_values().collect();
result.sort_by_key(|r| r.tier);
Ok(result)
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExtractedStat {
pub name: String,
pub modifier_types: Vec<String>,
pub occurrences: usize,
}
pub fn extract_stats_from_pak(pak_manifest_path: &Path) -> Result<Vec<ExtractedStat>> {
let content =
fs::read_to_string(pak_manifest_path).context("Failed to read pak_manifest.json")?;
let manifest: PakManifest =
serde_json::from_str(&content).context("Failed to parse pak_manifest.json")?;
let mut stats: HashMap<String, (std::collections::HashSet<String>, usize)> = HashMap::new();
let stat_pattern =
Regex::new(r"^([A-Z][a-zA-Z]+)_(Scale|Add|Value|Percent)_\d+_[A-F0-9]{32}$").unwrap();
let simple_stat_pattern = Regex::new(r"^([A-Z][a-zA-Z]+)_\d+_[A-F0-9]{32}$").unwrap();
let stat_prefixes = [
"Accuracy",
"Damage",
"CritDamage",
"FireRate",
"ReloadTime",
"ReloadSpeed",
"MagSize",
"Spread",
"Recoil",
"Sway",
"Ammo",
"AmmoCost",
"Capacity",
"Cooldown",
"Duration",
"Healing",
"Health",
"Impulse",
"Projectile",
"Radius",
"Regen",
"Speed",
"StatusChance",
"StatusDamage",
"ElementalPower",
"DamageRadius",
"EquipTime",
"PutDownTime",
"ZoomDuration",
"AccImpulse",
"AccRegen",
"AccDelay",
"ProjectilesPerShot",
];
for item in &manifest.items {
for prop in &item.property_names {
if let Some(cap) = stat_pattern.captures(prop) {
let stat_name = cap[1].to_string();
let modifier_type = cap[2].to_string();
let entry = stats
.entry(stat_name)
.or_insert_with(|| (std::collections::HashSet::new(), 0));
entry.0.insert(modifier_type);
entry.1 += 1;
}
if let Some(cap) = simple_stat_pattern.captures(prop) {
let stat_name = cap[1].to_string();
if stat_prefixes.contains(&stat_name.as_str()) {
let entry = stats
.entry(stat_name)
.or_insert_with(|| (std::collections::HashSet::new(), 0));
entry.1 += 1;
}
}
}
}
let mut result: Vec<ExtractedStat> = stats
.into_iter()
.map(|(name, (modifiers, count))| {
let mut modifier_types: Vec<String> = modifiers.into_iter().collect();
modifier_types.sort();
ExtractedStat {
name,
modifier_types,
occurrences: count,
}
})
.collect();
result.sort_by(|a, b| b.occurrences.cmp(&a.occurrences));
Ok(result)
}