use std::{
collections::HashSet,
fs::{File, OpenOptions},
io::{self, Seek, SeekFrom, Write},
path::Path,
string::FromUtf8Error,
};
use arboard::Clipboard;
use goblin::Object;
use goblin::error;
use mmap_io::{MemoryMappedFile, MmapMode};
use ratatui::{Frame, layout::Rect, widgets::ListState};
use crate::{
config::*,
editor::*,
global::calculator::Calculator,
header::header_view::{Elf, HeaderView, Pe},
hex::{hex_view::HexView, strings::FoundString},
input_history::InputHistory,
reader::Reader,
themes::*,
};
#[derive(Default)]
pub struct FileInfo {
pub file: Option<File>,
pub path: String,
pub is_read_only: bool,
pub name: String,
pub r#type: &'static str,
pub size: usize,
pub mmap: Option<MemoryMappedFile>,
}
impl FileInfo {
pub fn get_buffer(&mut self) -> &[u8] {
if let Some(mmap) = self.mmap.as_mut() {
return mmap.as_slice_bytes(0, self.size as u64).unwrap();
}
&[]
}
}
#[derive(Debug)]
pub struct TextView {
pub area_height: u16,
pub lines_to_show: usize,
pub scroll_offset: (u16, u16), pub table: &'static encoding_rs::Encoding,
}
pub struct Dz6Error {
pub message: String,
}
pub struct App {
pub calculator: Calculator,
pub clipboard: Result<Clipboard, arboard::Error>,
pub command_area: Rect,
pub command_input: InputHistory,
pub config: Config,
pub dialog_2nd_renderer: Option<fn(&mut App, &mut Frame)>,
pub dialog_renderer: Option<fn(&mut App, &mut Frame)>,
pub editor_view: AppView,
pub file_info: FileInfo,
pub header_view: HeaderView,
pub hex_view: HexView,
pub last_error: Dz6Error,
pub list_state: ListState,
pub log_scroll_offset: (u16, u16),
pub logs: Vec<String>,
pub reader: Reader,
pub running: bool,
pub screen: Rect,
pub state: UIState,
pub string_regex: String,
pub strings: Vec<FoundString>,
pub text_view: TextView,
}
impl App {
pub fn new() -> Self {
App {
calculator: Calculator::default(),
clipboard: Clipboard::new(),
command_area: Rect::default(),
command_input: InputHistory::default(),
config: Config {
database: true,
dim_control_chars: false,
dim_zeroes: true,
header_base: 16,
hex_mode_bytes_per_line: 16,
hex_mode_bytes_per_line_auto: false,
hex_mode_non_graphic_char: '.',
maximum_strings_to_show: 3000,
minimum_string_length: 4,
search_wrapscan: false,
search_ignorecase: false,
search_smartcase: false,
theme: DARK,
},
dialog_renderer: None,
dialog_2nd_renderer: None,
editor_view: AppView::Hex,
file_info: FileInfo::default(),
header_view: HeaderView {
..Default::default()
},
hex_view: HexView {
editing_hex: true,
highlights: HashSet::with_capacity(8),
..Default::default()
},
list_state: ListState::default(),
log_scroll_offset: (0, 0),
logs: Vec::with_capacity(100),
reader: Reader::new(),
running: true,
screen: Rect::default(),
state: UIState::Normal,
string_regex: String::new(),
strings: Vec::new(),
text_view: TextView {
area_height: 0,
lines_to_show: 0,
scroll_offset: (0, 0),
table: encoding_rs::UTF_8,
},
last_error: Dz6Error {
message: "Success".to_string(),
},
}
}
fn id_file(&mut self) -> error::Result<()> {
let buffer = self.file_info.get_buffer();
self.file_info.r#type = match Object::parse(buffer)? {
Object::COFF(_coff) => "COFF",
Object::Elf(elf) => {
self.header_view.elf = Some(Elf {
header: elf.header,
phdrs: elf.program_headers.clone(),
sections: elf.section_headers.clone(),
dynsymtab: elf.dynsyms.to_vec(),
dynstrtab: elf
.dynsyms
.iter()
.map(|s| s.st_name)
.filter_map(|idx| {
elf.dynstrtab.get_at(idx).map(|name| (idx, name.to_owned()))
})
.collect(),
symtab: elf.syms.to_vec(),
strtab: elf
.syms
.iter()
.map(|s| s.st_name)
.filter_map(|idx| elf.strtab.get_at(idx).map(|name| (idx, name.to_owned())))
.collect(),
relocs: [
elf.pltrelocs.to_vec(),
elf.dynrelas.to_vec(),
elf.dynrels.to_vec(),
]
.concat(),
});
"ELF"
}
Object::Mach(_mach) => "Mach-O",
Object::PE(pe) => {
let mut imports = Vec::new();
for imp in pe.imports {
imports.push(crate::header::header_view::PEImport {
dll: imp.dll.to_string(),
name: imp.name.to_string(),
offset: imp.offset,
ordinal: imp.ordinal,
rva: imp.rva,
_size: imp.size,
});
}
let mut exports = Vec::new();
for exp in pe.exports {
exports.push(crate::header::header_view::PEExport {
name: exp.name.unwrap_or_default().to_string(),
offset: exp.offset.unwrap_or_default(),
rva: exp.rva,
size: exp.size,
});
}
self.header_view.pe = Some(Pe {
dos_header: pe.header.dos_header,
coff_header: pe.header.coff_header,
optional_header: pe.header.optional_header,
sections: pe.sections,
imports,
exports,
});
"PE"
}
Object::TE(_magic) => "TE",
_ => "",
};
Ok(())
}
pub fn load_file(
&mut self,
filepath: &str,
initial_offset: usize,
read_only: bool,
) -> io::Result<()> {
let path = Path::new(&filepath);
if let Some(f) = path.file_name()
&& let Some(fname) = f.to_str()
{
self.file_info.name = String::from(fname);
self.file_info.path = String::from(filepath);
}
let meta = path.metadata()?;
if !read_only && let Ok(file) = OpenOptions::new().read(true).write(true).open(path) {
self.file_info.file = Some(file);
} else {
self.file_info.is_read_only = true;
}
if let Ok(mmap) = MemoryMappedFile::builder(path)
.mode(MmapMode::ReadOnly)
.open()
{
self.file_info.mmap = Some(mmap);
} else {
return Err(std::io::Error::other("could not open file"));
}
self.file_info.size = meta.len() as usize;
if self.file_info.size > 0 {
_ = self.id_file();
}
self.log(format!(
"filesize: {} (0x{:x})",
self.file_info.size, self.file_info.size
));
if initial_offset != 0 {
self.goto(0);
}
self.goto(initial_offset);
if self.config.database {
let _ = self.load_database();
}
Ok(())
}
pub fn reload_file(&mut self) {
let fp = self.file_info.path.clone();
self.load_file(&fp, self.hex_view.offset, self.file_info.is_read_only)
.expect("could not reload the file");
}
pub fn write_to_file(&mut self) -> io::Result<()> {
if self.file_info.file.is_none() {
return Err(io::Error::other("file not open"));
}
let mut total_written = 0;
if let Some(f) = &mut self.file_info.file {
for (k, v) in &self.hex_view.changed_bytes {
f.seek(SeekFrom::Start(*k as u64))?;
if let Ok(b) = u8::from_str_radix(v, 16) {
let buf = vec![b];
let written = f.write(&buf)?;
if written != 1 {
return Err(io::Error::other("could not write to file"));
}
total_written += written;
}
}
}
App::log(self, format!("{} bytes written to file", total_written));
self.hex_view.changed_bytes.clear();
Ok(())
}
pub fn read_u8(&mut self, offset: usize) -> Option<u8> {
if offset >= self.file_info.size {
return None;
}
let buffer = self.file_info.get_buffer();
Some(buffer[offset])
}
pub fn read_i8(&mut self, offset: usize) -> Option<i8> {
if offset >= self.file_info.size {
return None;
}
let buffer = self.file_info.get_buffer();
Some(buffer[offset] as i8)
}
pub fn read_u16(&mut self, offset: usize) -> Option<u16> {
if offset + 1 >= self.file_info.size {
return None;
}
let buffer = self.file_info.get_buffer();
let b1 = buffer[offset];
let b2 = buffer[offset + 1];
Some(u16::from_le_bytes([b1, b2]))
}
pub fn read_i16(&mut self, offset: usize) -> Option<i16> {
if offset + 1 >= self.file_info.size {
return None;
}
let buffer = self.file_info.get_buffer();
let b1 = buffer[offset];
let b2 = buffer[offset + 1];
Some(i16::from_le_bytes([b1, b2]))
}
pub fn read_u32(&mut self, offset: usize) -> Option<u32> {
if offset + 3 >= self.file_info.size {
return None;
}
let buffer = self.file_info.get_buffer();
let b1 = buffer[offset];
let b2 = buffer[offset + 1];
let b3 = buffer[offset + 2];
let b4 = buffer[offset + 3];
Some(u32::from_le_bytes([b1, b2, b3, b4]))
}
pub fn read_i32(&mut self, offset: usize) -> Option<i32> {
if offset + 3 >= self.file_info.size {
return None;
}
let buffer = self.file_info.get_buffer();
let b1 = buffer[offset];
let b2 = buffer[offset + 1];
let b3 = buffer[offset + 2];
let b4 = buffer[offset + 3];
Some(i32::from_le_bytes([b1, b2, b3, b4]))
}
pub fn read_u64(&mut self, offset: usize) -> Option<u64> {
if offset + 7 >= self.file_info.size {
return None;
}
let buffer = self.file_info.get_buffer();
let b1 = buffer[offset];
let b2 = buffer[offset + 1];
let b3 = buffer[offset + 2];
let b4 = buffer[offset + 3];
let b5 = buffer[offset + 4];
let b6 = buffer[offset + 5];
let b7 = buffer[offset + 6];
let b8 = buffer[offset + 7];
Some(u64::from_le_bytes([b1, b2, b3, b4, b5, b6, b7, b8]))
}
pub fn read_i64(&mut self, offset: usize) -> Option<i64> {
if offset + 7 >= self.file_info.size {
return None;
}
let buffer = self.file_info.get_buffer();
let b1 = buffer[offset];
let b2 = buffer[offset + 1];
let b3 = buffer[offset + 2];
let b4 = buffer[offset + 3];
let b5 = buffer[offset + 4];
let b6 = buffer[offset + 5];
let b7 = buffer[offset + 6];
let b8 = buffer[offset + 7];
Some(i64::from_le_bytes([b1, b2, b3, b4, b5, b6, b7, b8]))
}
pub fn _read_string(&mut self, offset: usize) -> Result<String, FromUtf8Error> {
let buffer = self.file_info.get_buffer();
let it = buffer.iter().skip(offset);
let mut v = Vec::new();
for e in it {
v.push(*e);
}
String::from_utf8(v)
}
}