use super::super::constants::*;
use super::super::source::MemorySource;
use anyhow::{bail, Result};
use byteorder::{ByteOrder, LE};
#[derive(Debug, Clone)]
pub struct FNamePool {
pub header_addr: usize,
pub current_block: u32,
pub current_cursor: u32,
pub blocks: Vec<usize>,
}
impl FNamePool {
pub fn discover(source: &dyn MemorySource) -> Result<Self> {
if let Ok(pool) = Self::discover_at_address(source, FNAMEPOOL_HEADER_ADDR) {
return Ok(pool);
}
eprintln!("SDK FNamePool location invalid, searching dynamically...");
let mut regions_to_search: Vec<_> = source
.regions()
.iter()
.filter(|r| r.is_readable())
.collect();
regions_to_search.sort_by_key(|r| {
if r.start >= 0x140000000 && r.start < 0x160000000 {
0 } else if r.start >= 0x1000000 && r.start < 0x140000000 {
1 } else {
2 }
});
for region in regions_to_search {
let data = match source.read_bytes(region.start, region.size().min(16 * 1024 * 1024)) {
Ok(d) => d,
Err(_) => continue,
};
for i in (0..data.len().saturating_sub(32)).step_by(8) {
let lock = LE::read_u64(&data[i..i + 8]);
let current_block = LE::read_u32(&data[i + 8..i + 12]);
let current_cursor = LE::read_u32(&data[i + 12..i + 16]);
let block0 = LE::read_u64(&data[i + 16..i + 24]) as usize;
if lock > 100 {
continue;
}
if current_block == 0 || current_block > 1000 {
continue;
}
if current_cursor == 0 || current_cursor > 0x100000 {
continue;
}
if block0 < 0x100000 || block0 > 0x800000000000 || block0 % 8 != 0 {
continue;
}
if let Ok(entry_data) = source.read_bytes(block0, 64) {
let header0 = LE::read_u16(&entry_data[0..2]);
let len0 = (header0 >> 6) as usize;
if len0 == 4 && &entry_data[2..6] == b"None" {
let header_addr = region.start + i;
eprintln!(
"Found FNamePool at {:#x}: lock={}, blocks={}, cursor={}, block0={:#x}",
header_addr, lock, current_block, current_cursor, block0
);
let num_blocks = (current_block + 1) as usize;
let blocks_data = source.read_bytes(header_addr + 16, num_blocks * 8)?;
let blocks: Vec<usize> = blocks_data
.chunks_exact(8)
.map(|c| LE::read_u64(c) as usize)
.collect();
return Ok(FNamePool {
header_addr,
current_block,
current_cursor,
blocks,
});
}
}
}
}
bail!("FNamePool header not found")
}
fn discover_at_address(source: &dyn MemorySource, addr: usize) -> Result<Self> {
let header_data = source.read_bytes(addr, 24)?;
let lock = LE::read_u64(&header_data[0..8]);
let current_block = LE::read_u32(&header_data[8..12]);
let current_cursor = LE::read_u32(&header_data[12..16]);
let block0 = LE::read_u64(&header_data[16..24]) as usize;
if current_block == 0 || current_block > 1000 {
bail!("FNamePool current_block {} invalid", current_block);
}
if block0 == 0 || block0 < MIN_VALID_POINTER || block0 > MAX_VALID_POINTER {
bail!("FNamePool block0 pointer {:#x} is invalid", block0);
}
let entry_data = source.read_bytes(block0, 8)?;
let header0 = LE::read_u16(&entry_data[0..2]);
let len0 = (header0 >> 6) as usize;
if len0 != 4 || &entry_data[2..6] != b"None" {
bail!("Block0 doesn't start with 'None' entry");
}
eprintln!(
"Found FNamePool at {:#x}: lock={}, blocks={}, cursor={}, block0={:#x}",
addr, lock, current_block, current_cursor, block0
);
let num_blocks = (current_block + 1) as usize;
let blocks_data = source.read_bytes(addr + 16, num_blocks * 8)?;
let blocks: Vec<usize> = blocks_data
.chunks_exact(8)
.map(|c| LE::read_u64(c) as usize)
.collect();
Ok(FNamePool {
header_addr: addr,
current_block,
current_cursor,
blocks,
})
}
pub fn discover_with_gnames(source: &dyn MemorySource, gnames_addr: usize) -> Result<Self> {
eprintln!(
"Searching for FNamePool header with Block0 = {:#x}...",
gnames_addr
);
let mut regions_to_search: Vec<_> = source
.regions()
.iter()
.filter(|r| r.is_readable())
.collect();
regions_to_search.sort_by_key(|r| {
if r.start >= 0x140000000 && r.start < 0x160000000 {
0
} else {
1
}
});
for region in regions_to_search {
if !region.is_readable() {
continue;
}
let data = match source.read_bytes(region.start, region.size().min(32 * 1024 * 1024)) {
Ok(d) => d,
Err(_) => continue,
};
for i in (0..data.len().saturating_sub(32)).step_by(8) {
let block0 = LE::read_u64(&data[i + 16..i + 24]) as usize;
if block0 != gnames_addr {
continue;
}
let lock = LE::read_u64(&data[i..i + 8]);
let current_block = LE::read_u32(&data[i + 8..i + 12]);
let current_cursor = LE::read_u32(&data[i + 12..i + 16]);
if lock > 100 || current_block == 0 || current_block > 1000 {
continue;
}
let header_addr = region.start + i;
eprintln!(
"Found FNamePool at {:#x}: lock={}, blocks={}, cursor={}, block0={:#x}",
header_addr, lock, current_block, current_cursor, block0
);
let num_blocks = (current_block + 1) as usize;
let blocks_data = source.read_bytes(header_addr + 16, num_blocks * 8)?;
let blocks: Vec<usize> = blocks_data
.chunks_exact(8)
.map(|c| LE::read_u64(c) as usize)
.collect();
return Ok(FNamePool {
header_addr,
current_block,
current_cursor,
blocks,
});
}
}
bail!("FNamePool header with Block0={:#x} not found", gnames_addr)
}
}