From d4b8ef032b3883b6498cc96d7c5f8162fb267597 Mon Sep 17 00:00:00 2001 From: Numbers Date: Mon, 2 Jun 2025 09:55:02 +0200 Subject: [PATCH] utility for performing small hex dumps --- sub/xpat/src/hexdump.rs | 87 +++++++++++++++++++++++++++++++++++++++ sub/xpat/src/lib.rs | 5 ++- sub/xpat/src/scannable.rs | 32 +++++++++++++- sub/xpat/src/scanner.rs | 2 +- 4 files changed, 123 insertions(+), 3 deletions(-) create mode 100644 sub/xpat/src/hexdump.rs diff --git a/sub/xpat/src/hexdump.rs b/sub/xpat/src/hexdump.rs new file mode 100644 index 0000000..caba39b --- /dev/null +++ b/sub/xpat/src/hexdump.rs @@ -0,0 +1,87 @@ +use crate::scannable::{ChunkIter, Scannable}; +use core::ops::{Bound, Range, RangeBounds, RangeFull}; +use core::fmt::{Display, Formatter}; + +const SEP: &str = " | "; + +pub struct HexDump<'s, T: Scannable + ?Sized, R: RangeBounds>(pub &'s T, pub R); + +pub fn hex< + 's, + T: Scannable + ?Sized, + R: RangeBounds +>( + data: &'s T, + range:R +) -> HexDump<'s, T, R> { + HexDump(data, range) +} + + +impl<'s, T: Scannable> HexDump<'s, T, RangeFull> { + pub fn new(scannable: &'s T) -> Self { + Self(scannable, ..) + } +} + + +impl<'s, T: Scannable + ?Sized, R: RangeBounds> Display for HexDump<'s, T, R> { + + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + + // calculate the end and the start addresses + let (start, end) = { + let r = self.0.range(); + let start = match self.1.start_bound() { + Bound::Included(i) => *i, + Bound::Excluded(i) => i.saturating_add(1), + Bound::Unbounded => 0, + }.max(r.start); + let end = match self.1.end_bound() { + Bound::Included(i) => *i, + Bound::Excluded(i) => i.saturating_sub(1), + Bound::Unbounded => usize::MAX, + }.min(r.end); + (start, end) + }; + + // the number of digits the address column should have + let digits = (end.ilog(16) as usize + 1).max(4); + + for (mut addr, chunk) in ChunkIter::new(self.0, start) { + for chunk in chunk.chunks(16) { + + //╶───╴Column╶────────────────────────────────╴ + write!(f, "{:0digits$X}{SEP}", addr, digits = digits)?; + + //╶───╴Bytes╶─────────────────────────────────╴ + for (i, byte) in chunk.iter().enumerate() { + if i != 0 { write!(f, " ")?; } + write!(f, "{byte:02X}")?; + } + for i in (chunk.len()..16) { + match i { + 0 => write!(f, " ")?, + _ => write!(f, " ")?, + } + } + + //╶───╴Text╶──────────────────────────────────╴ + write!(f, "{SEP}")?; + for &byte in chunk { + match byte { + 0x20..0x7e => write!(f, "{}", char::from(byte))?, + _ => write!(f, ".")?, + } + } + writeln!(f)?; + addr += chunk.len() + } + } + + let mut last_addr = 0usize; + let fchunk = self.0.chunk_at(0); + + Ok(()) + } +} diff --git a/sub/xpat/src/lib.rs b/sub/xpat/src/lib.rs index a909ce9..03a4ae0 100644 --- a/sub/xpat/src/lib.rs +++ b/sub/xpat/src/lib.rs @@ -6,7 +6,7 @@ pub mod atoms { pub mod scannable; pub mod scanner; - +pub mod hexdump; // // Export Preludes: @@ -16,6 +16,7 @@ pub mod prelude { pub use sub_macros::pattern; pub use crate::atoms::Pattern; pub use crate::scanner::Scanner; + pub use crate::hexdump::hex; } pub mod public { @@ -24,5 +25,7 @@ pub mod public { pub use crate::scanner::{ exec, scan_for_aob, make_aob }; + pub use crate::scannable::ChunkIter; + pub use crate::hexdump::HexDump; } diff --git a/sub/xpat/src/scannable.rs b/sub/xpat/src/scannable.rs index 911b1c3..e890072 100644 --- a/sub/xpat/src/scannable.rs +++ b/sub/xpat/src/scannable.rs @@ -12,7 +12,6 @@ pub trait Scannable { } - impl Scannable for [u8] { fn range(&self) -> Range { 0..self.len() } fn chunk_at(&self, address: usize) -> Option<&[u8]> { @@ -31,4 +30,35 @@ impl Scannable for (usize, &[u8]) { } } fn next_chunk(&self, _address: usize) -> Option<(usize, &[u8])> { None } +} + +pub struct ChunkIter<'l, T: Scannable + ?Sized>(&'l T, usize, bool); + +impl<'l, T: Scannable + ?Sized> ChunkIter<'l, T> { + pub const fn new(scannable : &'l T, start: usize) -> Self { + Self(scannable, start, true) + } +} + +impl<'l, T: Scannable + ?Sized> Iterator for ChunkIter<'l, T> { + type Item = (usize, &'l [u8]); + fn next(&mut self) -> Option { + + // if this is the first time being called, use chunk_at instead of next_chunk + if self.2 { + self.2 = false; + if let Some(chunk) = self.0.chunk_at(self.1) { + return Some((self.1, chunk)) + } + } + + match self.0.next_chunk(self.1) { + None => None, + Some((addr, block)) => { + self.1 = addr; + Some((addr, block)) + } + } + + } } \ No newline at end of file diff --git a/sub/xpat/src/scanner.rs b/sub/xpat/src/scanner.rs index 28c8408..8622f9b 100644 --- a/sub/xpat/src/scanner.rs +++ b/sub/xpat/src/scanner.rs @@ -326,7 +326,7 @@ pub fn scan_for_aob( let chunk = match bin.chunk_at(address) { Some(chunk) => chunk, - // the address is out of bounds, try to shift the address so its back in b ounds + // the address is out of bounds, try to shift the address so its back in bounds None => match bin.next_chunk(address) { // the next chunk is in bounds so we will just correct the address and use that chunk instead