use std::{fs, io::prelude::*, ops::Range};
use anyhow::Result;
use log::{debug, error};
use regex::bytes::Regex;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum UtilError {
#[error("insufficient file access")]
FileAccess,
#[error("invalid file format")]
FileFormat,
}
pub fn u64_i64(i: u64) -> i64 {
if i & 1 << 63 > 0 {
let bits = i & 0x7FFF_FFFF_FFFF_FFFF;
let bits = !bits;
let bits = bits + 1;
let bits = bits & 0x7FFF_FFFF_FFFF_FFFF;
-(bits as i64)
} else {
i as i64
}
}
pub fn align(i: u64, b: u64) -> u64 {
if b < 2 {
panic!("base `b` must be at least: 2");
}
let rem = i % b;
if rem == 0 {
i
} else {
i + (b - rem)
}
}
pub fn hexdump_ascii(b: u8) -> char {
if b.is_ascii_graphic() || b == b' ' {
b as char
} else {
'.'
}
}
pub fn hexdump(buf: &[u8], offset: usize) -> String {
let padding = " ";
let padding_size = 2;
let hex_col_size = 3;
let ascii_col_size = 1;
let prefix_size = 8 + 1;
let newline_size = 1;
let line_size =
(prefix_size + padding_size + 16 * hex_col_size + padding_size + 16 * ascii_col_size + newline_size) as usize;
let line_count = (align(buf.len() as u64, 0x10) / 0x10) as usize;
let mut ret = String::with_capacity(line_count * line_size);
let mut line = String::with_capacity(line_size);
let mut remaining_count = buf.len();
for line_index in 0..line_count {
let line_elem_count = 0x10.min(remaining_count);
let padding_elem_count = 0x10 - line_elem_count;
line.push_str(format!("{:08x}:", offset + 0x10 * line_index).as_str());
line.push_str(padding);
for elem in &buf[(line_index * 0x10)..(line_index * 0x10) + line_elem_count] {
line.push_str(format!("{:02x} ", elem).as_str());
}
for _ in 0..padding_elem_count {
line.push_str(" ");
}
line.push_str(padding);
for elem in &buf[(line_index * 0x10)..(line_index * 0x10) + line_elem_count] {
line.push(hexdump_ascii(*elem))
}
for _ in 0..padding_elem_count {
line.push(' ');
}
line.push_str(padding);
line.push('\n');
ret.push_str(line.as_str());
line.truncate(0x0);
remaining_count -= line_elem_count;
}
ret
}
pub fn read_file(filename: &str) -> Result<Vec<u8>> {
debug!("read_file: {:?}", filename);
let mut buf = Vec::new();
{
debug!("reading file: {}", filename);
let mut f = match fs::File::open(filename) {
Ok(f) => f,
Err(_) => {
error!("failed to open file: {}", filename);
return Err(UtilError::FileAccess.into());
}
};
let bytes_read = match f.read_to_end(&mut buf) {
Ok(c) => c,
Err(_) => {
error!("failed to read entire file: {}", filename);
return Err(UtilError::FileAccess.into());
}
};
debug!("read {} bytes", bytes_read);
if bytes_read < 0x10 {
error!("file too small: {}", filename);
return Err(UtilError::FileFormat.into());
}
}
Ok(buf)
}
pub fn find_ascii_strings<'a>(buf: &'a [u8]) -> Box<dyn Iterator<Item = (Range<usize>, String)> + 'a> {
lazy_static! {
static ref ASCII_RE: Regex = Regex::new("[ -~]{4,}").unwrap();
}
Box::new(ASCII_RE.captures_iter(buf).map(|cap| {
let mat = cap.get(0).unwrap();
let s = String::from_utf8(mat.as_bytes().to_vec()).unwrap();
(
Range {
start: mat.start(),
end: mat.end(),
},
s,
)
}))
}
pub fn find_unicode_strings<'a>(buf: &'a [u8]) -> Box<dyn Iterator<Item = (Range<usize>, String)> + 'a> {
lazy_static! {
static ref UNICODE_RE: Regex = Regex::new("([ -~]\x00){4,}").unwrap();
}
Box::new(UNICODE_RE.captures_iter(buf).map(|cap| {
let mat = cap.get(0).unwrap();
let bytes = mat.as_bytes();
let words: Vec<u16> = bytes
.chunks_exact(2)
.map(|w| u16::from(w[1]) << 8 | u16::from(w[0]))
.collect();
let s = String::from_utf16(&words).unwrap();
(
Range {
start: mat.start(),
end: mat.end(),
},
s,
)
}))
}