use crate::manifest;
use crate::serial::{PartEncoding, Token};
const CLASS_MOD_FIRMWARE_CATEGORY: u64 = 234;
const EQUIPMENT_FIRMWARE_CATEGORY: i64 = 247;
#[derive(Debug, Clone)]
pub struct DetectedFirmware {
pub name: String,
pub index: i64,
pub category: i64,
pub transferred: bool,
}
fn has_firmware_flag(tokens: &[Token]) -> bool {
tokens
.iter()
.any(|t| matches!(t, Token::String(s) if s == "ft"))
}
pub fn detect(tokens: &[Token], item_category: i64) -> Option<DetectedFirmware> {
let transferred = has_firmware_flag(tokens);
let mut result = if crate::skills::is_class_mod(item_category) {
detect_class_mod(tokens)?
} else {
detect_equipment(tokens, item_category)?
};
result.transferred = transferred;
Some(result)
}
fn detect_class_mod(tokens: &[Token]) -> Option<DetectedFirmware> {
for token in tokens {
if let Token::Part { index, values, .. } = token {
if *index == CLASS_MOD_FIRMWARE_CATEGORY && !values.is_empty() {
let fw_idx = values[0] as i64;
let name = manifest::part_name(CLASS_MOD_FIRMWARE_CATEGORY as i64, fw_idx)
.unwrap_or("unknown")
.to_string();
return Some(DetectedFirmware {
name,
index: fw_idx,
category: CLASS_MOD_FIRMWARE_CATEGORY as i64,
transferred: false, });
}
}
}
None
}
fn detect_equipment(tokens: &[Token], item_category: i64) -> Option<DetectedFirmware> {
let fw_category = equipment_firmware_category(item_category);
for token in tokens.iter().rev() {
match token {
Token::Var { val, .. } => {
let fw_idx = *val as i64;
if let Some(name) = manifest::part_name(fw_category, fw_idx) {
if name.contains("firmware") {
return Some(DetectedFirmware {
name: name.to_string(),
index: fw_idx,
category: fw_category,
transferred: false, });
}
}
}
Token::SoftSeparator => break,
_ => continue,
}
}
None
}
fn equipment_firmware_category(item_category: i64) -> i64 {
let cat_name = manifest::category_name(item_category).unwrap_or("");
let lower = cat_name.to_lowercase();
if lower.contains("repair kit") {
243
} else if lower.contains("heavy weapon gadget") || lower.contains("turret gadget") {
244
} else if lower.contains("grenade gadget")
|| lower.contains("terminal gadget")
|| lower.contains("weapon gadget")
{
245
} else if lower.contains("shield") {
246
} else if lower.contains("enhancement") {
247
} else {
EQUIPMENT_FIRMWARE_CATEGORY
}
}
pub fn resolve_firmware(name: &str, item_category: i64) -> Result<(i64, i64), String> {
let fw_category = if crate::skills::is_class_mod(item_category) {
CLASS_MOD_FIRMWARE_CATEGORY as i64
} else {
equipment_firmware_category(item_category)
};
let with_prefix = if name.starts_with("part_firmware_") {
name.to_string()
} else {
format!("part_firmware_{}", name)
};
if let Some(idx) = manifest::part_index(fw_category, &with_prefix) {
return Ok((fw_category, idx));
}
if let Some(idx) = manifest::part_index(fw_category, name) {
return Ok((fw_category, idx));
}
let lower = name.to_lowercase().replace(' ', "_");
let with_prefix_lower = format!("part_firmware_{}", lower);
if let Some(idx) = manifest::part_index(fw_category, &with_prefix_lower) {
return Ok((fw_category, idx));
}
Err(format!(
"firmware '{}' not found in category {}",
name, fw_category
))
}
pub fn available_firmware(item_category: i64) -> Vec<(i64, String)> {
let fw_category = if crate::skills::is_class_mod(item_category) {
CLASS_MOD_FIRMWARE_CATEGORY as i64
} else {
equipment_firmware_category(item_category)
};
let mut result = Vec::new();
for idx in 1..=300 {
if let Some(name) = manifest::part_name(fw_category, idx) {
if name.contains("firmware") {
result.push((idx, name.to_string()));
}
}
}
result
}
pub fn apply(tokens: &[Token], fw_index: i64, item_category: i64) -> Vec<Token> {
if crate::skills::is_class_mod(item_category) {
if detect_class_mod(tokens).is_some() {
replace_class_mod_firmware(tokens, fw_index)
} else {
add_class_mod_firmware(tokens, fw_index)
}
} else if detect_equipment(tokens, item_category).is_some() {
replace_equipment_firmware(tokens, fw_index, item_category)
} else {
add_equipment_firmware(tokens, fw_index, item_category)
}
}
fn replace_class_mod_firmware(tokens: &[Token], fw_index: i64) -> Vec<Token> {
let mut result = tokens.to_vec();
for token in &mut result {
if let Token::Part { index, values, .. } = token {
if *index == CLASS_MOD_FIRMWARE_CATEGORY {
*values = vec![fw_index as u64];
return result;
}
}
}
result
}
fn replace_equipment_firmware(tokens: &[Token], fw_index: i64, item_category: i64) -> Vec<Token> {
let fw_category = equipment_firmware_category(item_category);
let mut result = tokens.to_vec();
for i in (0..result.len()).rev() {
match &result[i] {
Token::Var { val, .. } => {
let idx = *val as i64;
if let Some(name) = manifest::part_name(fw_category, idx) {
if name.contains("firmware") {
result[i] = Token::VarInt(fw_index as u64);
return result;
}
}
}
Token::SoftSeparator => break,
_ => continue,
}
}
result
}
fn add_class_mod_firmware(tokens: &[Token], fw_index: i64) -> Vec<Token> {
let mut result = tokens.to_vec();
let insert_pos = result
.iter()
.rposition(|t| matches!(t, Token::Separator))
.unwrap_or(result.len());
result.insert(
insert_pos,
Token::Part {
index: CLASS_MOD_FIRMWARE_CATEGORY,
values: vec![fw_index as u64],
encoding: PartEncoding::Single,
},
);
result
}
fn add_equipment_firmware(tokens: &[Token], fw_index: i64, item_category: i64) -> Vec<Token> {
let fw_pool = equipment_firmware_category(item_category) as u64;
let mut result = tokens.to_vec();
let last_part_pos = result.iter().rposition(|t| matches!(t, Token::Part { .. }));
let has_trailing = last_part_pos.and_then(|pp| {
result[pp..]
.iter()
.position(|t| matches!(t, Token::SoftSeparator))
.map(|p| p + pp)
});
if let Some(soft_pos) = has_trailing {
let insert_pos = result[soft_pos..]
.iter()
.position(|t| matches!(t, Token::Separator))
.map(|p| p + soft_pos)
.unwrap_or(result.len());
result.insert(insert_pos, Token::VarInt(fw_index as u64));
} else {
let target_pos = result
.iter()
.rposition(|t| matches!(t, Token::Part { index, .. } if *index == fw_pool));
if let Some(pos) = target_pos {
if let Token::Part { index, values, .. } = &result[pos] {
let old_values = values.clone();
let old_index = *index;
result[pos] = Token::Part {
index: old_index,
values: vec![],
encoding: PartEncoding::List,
};
let mut insert_pos = pos + 1;
result.insert(insert_pos, Token::SoftSeparator);
insert_pos += 1;
for val in &old_values {
result.insert(insert_pos, Token::VarInt(*val));
insert_pos += 1;
}
result.insert(insert_pos, Token::VarInt(fw_index as u64));
}
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_has_firmware_flag() {
let with = vec![Token::String("ft".to_string())];
let without = vec![Token::VarInt(9)];
assert!(has_firmware_flag(&with));
assert!(!has_firmware_flag(&without));
}
}