use crate::memory::{self, MemorySource};
use anyhow::{Context, Result};
use byteorder::ByteOrder;
pub fn handle_fname(source: &dyn MemorySource, index: u32, debug: bool) -> Result<()> {
match memory::FNamePool::discover(source) {
Ok(pool) => {
println!("FNamePool found at {:#x}", pool.header_addr);
println!(" Blocks: {}", pool.blocks.len());
println!(" Cursor: {}", pool.current_cursor);
let reader = memory::FNameReader::new(pool);
if debug {
reader.debug_read(source, index)?;
}
let mut reader = reader;
match reader.read_name(source, index) {
Ok(name) => {
println!("\nFName[{}] = \"{}\"", index, name);
let block = (index & 0x3FFFFFFF) >> 16;
let offset = ((index & 0xFFFF) * 2) as usize;
println!(" Block: {}, Offset: {:#x}", block, offset);
}
Err(e) => {
eprintln!("Failed to read FName[{}]: {}", index, e);
if !debug {
reader.debug_read(source, index)?;
}
}
}
}
Err(e) => {
eprintln!("FNamePool::discover failed: {}", e);
let gnames = memory::discover_gnames(source).context("Failed to find GNames pool")?;
println!("Using legacy FName reader (block 0 only)");
let mut reader = memory::FNameReader::new_legacy(gnames.address);
match reader.read_name(source, index) {
Ok(name) => println!("FName[{}] = \"{}\"", index, name),
Err(e) => eprintln!("Failed to read FName[{}]: {}", index, e),
}
}
}
Ok(())
}
pub fn handle_fname_search(source: &dyn MemorySource, query: &str) -> Result<()> {
let pool = memory::FNamePool::discover(source).context("Failed to discover FNamePool")?;
println!(
"Searching for \"{}\" across {} FName blocks...",
query,
pool.blocks.len()
);
let search_bytes = query.as_bytes();
let mut found = Vec::new();
for (block_idx, &block_addr) in pool.blocks.iter().enumerate() {
if block_addr == 0 {
continue;
}
let block_data = match source.read_bytes(block_addr, 64 * 1024) {
Ok(d) => d,
Err(_) => continue,
};
for (pos, window) in block_data.windows(search_bytes.len()).enumerate() {
if window == search_bytes {
if pos >= 2 {
let header = &block_data[pos - 2..pos];
let header_val = byteorder::LE::read_u16(header);
let len = (header_val >> 6) as usize;
if len > 0 && len <= 1024 {
let name_start = pos - 2 + 2;
let name_end = name_start + len;
if name_end <= block_data.len() {
let full_name =
String::from_utf8_lossy(&block_data[name_start..name_end]);
let byte_offset = pos - 2;
let fname_index =
((block_idx as u32) << 16) | ((byte_offset / 2) as u32);
found.push((
fname_index,
block_idx,
byte_offset,
full_name.to_string(),
));
}
}
}
}
}
}
if found.is_empty() {
println!("No matches found for \"{}\"", query);
} else {
println!("Found {} matches:", found.len());
for (fname_index, block_idx, byte_offset, name) in found.iter().take(50) {
println!(
" FName[{:#x}] = \"{}\" (block {}, offset {:#x})",
fname_index, name, block_idx, byte_offset
);
}
if found.len() > 50 {
println!(" ... and {} more", found.len() - 50);
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::memory::source::tests::MockMemorySource;
#[test]
fn test_handle_fname_with_invalid_pool() {
let source = MockMemorySource::new(vec![], 0x1000);
let result = handle_fname(&source, 0, false);
assert!(result.is_err() || result.is_ok());
}
#[test]
fn test_handle_fname_search_empty_query() {
let source = MockMemorySource::new(vec![], 0x1000);
let result = handle_fname_search(&source, "test");
assert!(result.is_err());
}
#[test]
fn test_fname_index_breakdown() {
let index: u32 = 0x0001_0010; let block = (index & 0x3FFFFFFF) >> 16;
let offset = ((index & 0xFFFF) * 2) as usize;
assert_eq!(block, 1);
assert_eq!(offset, 0x20);
}
#[test]
fn test_fname_index_calculation() {
let block_idx: u32 = 5;
let byte_offset: u32 = 0x100;
let fname_index = (block_idx << 16) | (byte_offset / 2);
assert_eq!(fname_index, 0x0005_0080);
}
}