use super::AMX_DBG_MAGIC;
use super::types::{
AmxDbg, DbgError, DbgFile, DbgLine, DbgSymDim, DbgSymbol, DbgTag, Ident, VClass,
};
const HDR_SIZE: usize = 22;
const LINE_SIZE: usize = 8;
struct Reader<'a> {
buf: &'a [u8],
pos: usize,
}
impl<'a> Reader<'a> {
fn new(buf: &'a [u8]) -> Self {
Self { buf, pos: 0 }
}
fn u8(&mut self) -> Result<u8, DbgError> {
let b = *self.buf.get(self.pos).ok_or(DbgError::Truncated)?;
self.pos += 1;
Ok(b)
}
fn u16(&mut self) -> Result<u16, DbgError> {
let s = self.take(2)?;
Ok(u16::from_le_bytes([s[0], s[1]]))
}
fn i16(&mut self) -> Result<i16, DbgError> {
let s = self.take(2)?;
Ok(i16::from_le_bytes([s[0], s[1]]))
}
fn count(&mut self) -> Result<usize, DbgError> {
Ok(usize::from(self.u16()?))
}
fn u32(&mut self) -> Result<u32, DbgError> {
let s = self.take(4)?;
Ok(u32::from_le_bytes([s[0], s[1], s[2], s[3]]))
}
fn peek_u32(&self) -> Option<u32> {
let s = self.buf.get(self.pos..self.pos + 4)?;
Some(u32::from_le_bytes([s[0], s[1], s[2], s[3]]))
}
fn remaining(&self) -> usize {
self.buf.len().saturating_sub(self.pos)
}
fn i32(&mut self) -> Result<i32, DbgError> {
let s = self.take(4)?;
Ok(i32::from_le_bytes([s[0], s[1], s[2], s[3]]))
}
fn take(&mut self, n: usize) -> Result<&'a [u8], DbgError> {
let end = self.pos.checked_add(n).ok_or(DbgError::Truncated)?;
let s = self.buf.get(self.pos..end).ok_or(DbgError::Truncated)?;
self.pos = end;
Ok(s)
}
fn cstr(&mut self) -> Result<String, DbgError> {
let start = self.pos;
while *self.buf.get(self.pos).ok_or(DbgError::UnterminatedName)? != 0 {
self.pos += 1;
}
let name = self.buf[start..self.pos]
.iter()
.map(|&b| b as char)
.collect();
self.pos += 1; Ok(name)
}
}
pub fn parse(data: &[u8]) -> Result<AmxDbg, DbgError> {
let mut r = Reader::new(data);
let _size = r.i32()?;
let magic = r.u16()?;
if magic != AMX_DBG_MAGIC {
return Err(DbgError::BadMagic(magic));
}
let file_version = r.u8()?;
let amx_version = r.u8()?;
let _flags = r.i16()?;
let files = r.count()?;
let lines = r.count()?;
let symbols = r.count()?;
let tags = r.count()?;
let automatons = r.count()?;
let states = r.count()?;
debug_assert_eq!(r.pos, HDR_SIZE);
let mut file_tbl = Vec::with_capacity(safe_cap(files, 5, r.remaining()));
for _ in 0..files {
let address = r.u32()?;
let name = r.cstr()?;
file_tbl.push(DbgFile { address, name });
}
let line_tbl = read_line_table(&mut r, lines)?;
let mut sym_tbl = Vec::with_capacity(safe_cap(symbols, 19, r.remaining()));
for _ in 0..symbols {
let address = r.u32()?;
let tag = r.i16()?;
let codestart = r.u32()?;
let codeend = r.u32()?;
let ident = Ident::from_byte(r.u8()?);
let vclass = VClass::from_byte(r.u8()?);
let dim = r.count()?;
let name = r.cstr()?;
let mut dims = Vec::with_capacity(safe_cap(dim, 6, r.remaining()));
for _ in 0..dim {
let dtag = r.i16()?;
let size = r.u32()?;
dims.push(DbgSymDim { tag: dtag, size });
}
sym_tbl.push(DbgSymbol {
address,
tag,
codestart,
codeend,
ident,
vclass,
name,
dims,
});
}
let mut tag_tbl = Vec::with_capacity(safe_cap(tags, 3, r.remaining()));
for _ in 0..tags {
let tag = r.i16()?;
let name = r.cstr()?;
tag_tbl.push(DbgTag { tag, name });
}
for _ in 0..automatons {
let _automaton = r.i16()?;
let _address = r.u32()?;
let _name = r.cstr()?;
}
for _ in 0..states {
let _state = r.i16()?;
let _automaton = r.i16()?;
let _name = r.cstr()?;
}
Ok(AmxDbg {
file_version,
amx_version,
files: file_tbl,
lines: line_tbl,
symbols: sym_tbl,
tags: tag_tbl,
})
}
fn safe_cap(count: usize, entry_min: usize, remaining: usize) -> usize {
count.min(remaining / entry_min.max(1))
}
fn read_line_table(r: &mut Reader, lines: usize) -> Result<Vec<DbgLine>, DbgError> {
let overflow_possible = lines > usize::try_from(i16::MAX).unwrap_or(0);
let max_lines = r.remaining() / LINE_SIZE;
let mut line_tbl = Vec::with_capacity(safe_cap(lines, LINE_SIZE, r.remaining()));
let mut to_read = lines;
let mut last_addr: Option<u32> = None;
loop {
for _ in 0..to_read {
if line_tbl.len() >= max_lines {
break;
}
let address = r.u32()?;
let line = r.i32()?.saturating_add(1);
line_tbl.push(DbgLine { address, line });
last_addr = Some(address);
}
if !overflow_possible {
break; }
let Some(prev) = last_addr else { break };
if line_tbl.len() >= max_lines {
break;
}
let Some(next_addr) = r.peek_u32() else { break };
if next_addr > prev {
to_read = 1usize << 16;
} else {
break;
}
}
Ok(line_tbl)
}