From 28a15ea37920c988e2a2e9f5f223b64402872f14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Merc=C3=AAs?= Date: Thu, 5 Mar 2026 16:08:21 -0300 Subject: [PATCH 01/13] Add basic header view --- Cargo.lock | 125 ++++++++++++++++++++++++++++++++------ Cargo.toml | 2 + src/app.rs | 36 ++++++++--- src/draw.rs | 25 +++++++- src/editor.rs | 4 +- src/events.rs | 3 +- src/global/events.rs | 2 +- src/header/draw.rs | 91 +++++++++++++++++++++++++++ src/header/events.rs | 26 ++++++++ src/header/header_view.rs | 7 +++ src/header/mod.rs | 3 + src/main.rs | 1 + 12 files changed, 293 insertions(+), 32 deletions(-) create mode 100644 src/header/draw.rs create mode 100644 src/header/events.rs create mode 100644 src/header/header_view.rs create mode 100644 src/header/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 30185f4..a9720a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -159,6 +159,25 @@ version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" +[[package]] +name = "capstone" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f442ae0f2f3f1b923334b4a5386c95c69c1cfa072bafa23d6fae6d9682eb1dd4" +dependencies = [ + "capstone-sys", + "static_assertions", +] + +[[package]] +name = "capstone-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e8087cab6731295f5a2a2bd82989ba4f41d3a428aab2e7c98d8f4db38aac05" +dependencies = [ + "cc", +] + [[package]] name = "castaway" version = "0.2.4" @@ -168,6 +187,16 @@ dependencies = [ "rustversion", ] +[[package]] +name = "cc" +version = "1.2.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" +dependencies = [ + "find-msvc-tools", + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.4" @@ -440,11 +469,13 @@ name = "dz6" version = "0.5.1" dependencies = [ "arboard", + "capstone", "clap", "crossterm", "directories-next", "encoding_rs", "evalexpr", + "goblin", "hex", "memchr", "mmap-io", @@ -529,6 +560,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + [[package]] name = "finl_unicode" version = "1.4.0" @@ -598,23 +635,34 @@ checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 5.3.0", "wasip2", ] [[package]] name = "getrandom" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 6.0.0", "wasip2", "wasip3", ] +[[package]] +name = "goblin" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "983a6aafb3b12d4c41ea78d39e189af4298ce747353945ff5105b54a056e5cd9" +dependencies = [ + "log", + "plain", + "scroll", +] + [[package]] name = "hashbrown" version = "0.15.5" @@ -716,9 +764,9 @@ checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "js-sys" -version = "0.3.90" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14dc6f6450b3f6d4ed5b16327f38fed626d375a886159ca555bd7822c0c3a5a6" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" dependencies = [ "once_cell", "wasm-bindgen", @@ -761,11 +809,10 @@ checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" [[package]] name = "libredox" -version = "0.1.12" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" dependencies = [ - "bitflags 2.11.0", "libc", ] @@ -1163,6 +1210,12 @@ dependencies = [ "siphasher", ] +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + [[package]] name = "portable-atomic" version = "1.13.1" @@ -1196,9 +1249,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.44" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -1209,6 +1262,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "rand" version = "0.8.5" @@ -1398,6 +1457,26 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scroll" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1257cd4248b4132760d6524d6dda4e053bc648c9070b960929bf50cfb1e7add" +dependencies = [ + "scroll_derive", +] + +[[package]] +name = "scroll_derive" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed76efe62313ab6610570951494bdaa81568026e0318eaa55f167de70eeea67d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "semver" version = "1.0.27" @@ -1473,6 +1552,12 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook" version = "0.3.18" @@ -1804,7 +1889,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" dependencies = [ "atomic", - "getrandom 0.4.1", + "getrandom 0.4.2", "js-sys", "wasm-bindgen", ] @@ -1850,9 +1935,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.113" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60722a937f594b7fde9adb894d7c092fc1bb6612897c46368d18e7a20208eff2" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" dependencies = [ "cfg-if", "once_cell", @@ -1863,9 +1948,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.113" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fac8c6395094b6b91c4af293f4c79371c163f9a6f56184d2c9a85f5a95f3950" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1873,9 +1958,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.113" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3fabce6159dc20728033842636887e4877688ae94382766e00b180abac9d60" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" dependencies = [ "bumpalo", "proc-macro2", @@ -1886,9 +1971,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.113" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de0e091bdb824da87dc01d967388880d017a0a9bc4f3bdc0d86ee9f9336e3bb5" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" dependencies = [ "unicode-ident", ] diff --git a/Cargo.toml b/Cargo.toml index 344461d..5e10a05 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,11 +12,13 @@ categories = ["command-line-utilities", "security"] [dependencies] arboard = { version = "3", default-features = false } +capstone = "0.14.0" clap = { version = "4.5.49", features = ["derive"] } crossterm = "0.29.0" directories-next = "2.0.0" encoding_rs = "0.8.35" evalexpr = "13.1.*" +goblin = "0.10.5" hex = "0.4.3" memchr = "2.8.0" mmap-io = "0.9.4" diff --git a/src/app.rs b/src/app.rs index 83524be..8820ada 100644 --- a/src/app.rs +++ b/src/app.rs @@ -6,6 +6,8 @@ use std::{ }; use arboard::Clipboard; +use goblin::Object; +use goblin::error; use mmap_io::{MemoryMappedFile, MmapMode}; use ratatui::{Frame, layout::Rect, widgets::ListState}; @@ -13,6 +15,7 @@ use crate::{ config::*, editor::*, global::calculator::Calculator, + header::header_view::HeaderView, hex::{hex_view::HexView, strings::FoundString}, input_history::InputHistory, reader::Reader, @@ -68,7 +71,9 @@ pub struct App { pub dialog_renderer: Option, 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, @@ -79,7 +84,6 @@ pub struct App { pub string_regex: String, pub strings: Vec, pub text_view: TextView, - pub last_error: Dz6Error, } impl App { @@ -107,6 +111,7 @@ impl App { dialog_2nd_renderer: None, editor_view: AppView::Hex, file_info: FileInfo::default(), + header_view: HeaderView::default(), hex_view: HexView { editing_hex: true, highlights: HashSet::with_capacity(8), @@ -134,14 +139,26 @@ impl App { } /// this function tries to identify a file type; this is a boilerplate implementation. - fn id_file(&mut self) { + fn id_file(&mut self) -> error::Result<()> { let buffer = self.file_info.get_buffer(); - self.file_info.r#type = match buffer[0] { - 0x7f => "ELF", - 0xca | 0xcf => "Mach-O", - 0x4d => "PE", + + self.file_info.r#type = match Object::parse(&buffer)? { + Object::COFF(_coff) => "COFF", + Object::Elf(_elf) => "ELF", + Object::Mach(_mach) => "Mach-O", + Object::PE(_pe) => "PE", + Object::TE(_magic) => "TE", _ => "", - } + }; + + // self.file_info.r#type = match buffer[0] { + // 0x7f => "ELF", + // 0xca | 0xcf => "Mach-O", + // 0x4d => "PE", + // _ => "", + // }; + + Ok(()) } /// load a file @@ -182,7 +199,7 @@ impl App { self.file_info.size = meta.len() as usize; if self.file_info.size > 0 { - self.id_file(); + _ = self.id_file(); } self.log(format!( @@ -199,6 +216,9 @@ impl App { if self.config.database { let _ = self.load_database(); } + + self.header_view.list_state.select_first(); + Ok(()) } diff --git a/src/draw.rs b/src/draw.rs index f7a0c80..65ffc56 100644 --- a/src/draw.rs +++ b/src/draw.rs @@ -3,7 +3,7 @@ use ratatui::{Frame, prelude::*, widgets::Paragraph}; use crate::{ app::App, editor::AppView, - global, + global, header, hex::{self, comment}, ruler, text, }; @@ -80,6 +80,29 @@ pub fn draw(frame: &mut Frame, app: &mut App) { text::draw::text_contents_draw(app, frame, horizontal_layout[0]); app.text_view.area_height = horizontal_layout[0].height; } + AppView::Header => { + let constraints = vec![ + Constraint::Percentage(100), // middle area (text content) + Constraint::Length(1), // status bar + Constraint::Length(1), // command bar + ]; + + let vertical_layout = Layout::default() + .direction(Direction::Vertical) + .constraints(constraints) + .split(frame.area()); + + // Draw status bar at the bottom + global::status_bar::status_bar_draw(app, frame, vertical_layout[1]); + + let horizontal_layout = Layout::default() + .direction(Direction::Horizontal) + .constraints(vec![Constraint::Percentage(100)]) + .split(vertical_layout[0]); + + header::draw::header_contents_draw(app, frame, horizontal_layout[0]); + app.text_view.area_height = horizontal_layout[0].height; + } } // The right event handler function is set by the keypress diff --git a/src/editor.rs b/src/editor.rs index 95bdfb7..97cbb1a 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -2,13 +2,15 @@ pub enum AppView { Text, Hex, + Header, } impl AppView { pub fn next(&mut self) { match self { - AppView::Text => *self = AppView::Hex, AppView::Hex => *self = AppView::Text, + AppView::Text => *self = AppView::Header, + AppView::Header => *self = AppView::Hex, } } } diff --git a/src/events.rs b/src/events.rs index 778697f..defb9ed 100644 --- a/src/events.rs +++ b/src/events.rs @@ -2,11 +2,11 @@ use ratatui::crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind}; use std::io::Result; use crate::app::App; -use crate::commands; use crate::editor::{AppView, UIState}; use crate::global; use crate::hex; use crate::text; +use crate::{commands, header}; pub fn handle_dialog_error_events(app: &mut App, key: KeyEvent) -> Result { match key.code { @@ -29,6 +29,7 @@ pub fn handle_events(app: &mut App) -> Result { match app.editor_view { AppView::Hex => hex::events::hex_mode_events(app, key)?, AppView::Text => text::events::text_mode_events(app, key)?, + AppView::Header => header::events::header_view_events(app, key)?, } } UIState::DialogHelp => handle_dialog_error_events(app, key)?, diff --git a/src/global/events.rs b/src/global/events.rs index f3fdb4a..452c9e6 100644 --- a/src/global/events.rs +++ b/src/global/events.rs @@ -7,7 +7,7 @@ use std::io::Result; pub fn handle_global_events(app: &mut App, key: KeyEvent) -> Result { match key.code { // switch views - KeyCode::Enter => app.switch_editor_view(), + KeyCode::Tab => app.switch_editor_view(), // log window KeyCode::Char('l') => { if key.modifiers.contains(KeyModifiers::ALT) { diff --git a/src/header/draw.rs b/src/header/draw.rs new file mode 100644 index 0000000..af41e51 --- /dev/null +++ b/src/header/draw.rs @@ -0,0 +1,91 @@ +use std::fmt::{Display, LowerHex}; + +use ratatui::{ + Frame, + layout::Rect, + widgets::{Block, Borders, Clear, List, ListItem}, +}; + +use crate::app::App; + +use goblin::error; +use goblin::pe::PE; + +fn add_to_list_hex(items: &mut Vec, label: &str, number: T) { + items.push(ListItem::new(format!("{:}: {:#x}", label, number))); +} + +fn add_to_list_dec(items: &mut Vec, label: &str, number: T) { + items.push(ListItem::new(format!("{:}: {:}", label, number))); +} + +fn header_contents_pe_draw(app: &mut App, frame: &mut Frame, area: Rect) -> error::Result<()> { + let buffer = app.file_info.get_buffer(); + let pe = PE::parse(&buffer)?; + let mut items = Vec::new(); + + add_to_list_hex( + &mut items, + "Characteristics", + pe.header.coff_header.characteristics, + ); + add_to_list_hex(&mut items, "Machine", pe.header.coff_header.machine); + add_to_list_hex( + &mut items, + "Compilation Timestamp", + pe.header.coff_header.time_date_stamp, + ); + + if let Some(opt) = pe.header.optional_header { + let ep_rva = opt.standard_fields.address_of_entry_point; + for sec in &pe.sections { + if ep_rva >= sec.virtual_address && ep_rva < sec.virtual_address + sec.virtual_size { + //TODO check over/underflow + app.header_view.entrypoint = ep_rva - sec.virtual_address + sec.pointer_to_raw_data; + } + } + + add_to_list_hex( + &mut items, + "Entrypoint", + opt.standard_fields.address_of_entry_point, + ); + add_to_list_hex(&mut items, "Magic", opt.standard_fields.magic); + add_to_list_dec( + &mut items, + "Minor linker version", + opt.standard_fields.minor_linker_version, + ); + add_to_list_dec( + &mut items, + "Major linker version", + opt.standard_fields.major_linker_version, + ); + } + + for sec in &pe.sections { + if let Ok(name) = sec.name() { + items.push(ListItem::new(name.to_string())); + } + } + + for lib in pe.libraries { + items.push(ListItem::new(lib)); + } + + let list = List::new(items) + .style(app.config.theme.main) + .highlight_style(app.config.theme.highlight) + .block(Block::new().borders(Borders::all())); + + frame.render_widget(Clear, area); + frame.render_stateful_widget(list, area, &mut app.header_view.list_state); + + Ok(()) +} + +pub fn header_contents_draw(app: &mut App, frame: &mut Frame, area: Rect) { + if app.file_info.r#type == "PE" { + let _ = header_contents_pe_draw(app, frame, area); + } +} diff --git a/src/header/events.rs b/src/header/events.rs new file mode 100644 index 0000000..4ff0d5e --- /dev/null +++ b/src/header/events.rs @@ -0,0 +1,26 @@ +use std::io::Result; + +use ratatui::crossterm::event::{KeyCode, KeyEvent}; + +use crate::{app::App, editor::AppView}; + +pub fn header_view_events(app: &mut App, key: KeyEvent) -> Result { + match key.code { + KeyCode::Up | KeyCode::Char('k') => { + app.header_view.list_state.select_previous(); + } + KeyCode::Down | KeyCode::Char('j') => { + app.header_view.list_state.select_next(); + } + KeyCode::Enter => { + if let Some(idx) = app.header_view.list_state.selected() { + if idx == 3 { + app.goto(app.header_view.entrypoint as usize); + app.editor_view = AppView::Hex; + } + } + } + _ => {} + } + Ok(false) +} diff --git a/src/header/header_view.rs b/src/header/header_view.rs new file mode 100644 index 0000000..60b3c99 --- /dev/null +++ b/src/header/header_view.rs @@ -0,0 +1,7 @@ +use ratatui::widgets::ListState; + +#[derive(Default, Debug)] +pub struct HeaderView { + pub list_state: ListState, + pub entrypoint: u32, +} diff --git a/src/header/mod.rs b/src/header/mod.rs new file mode 100644 index 0000000..ec3f867 --- /dev/null +++ b/src/header/mod.rs @@ -0,0 +1,3 @@ +pub mod draw; +pub mod events; +pub mod header_view; diff --git a/src/main.rs b/src/main.rs index 41e6a40..b2af6a6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,7 @@ mod draw; mod editor; mod events; mod global; +mod header; mod hex; mod initfile; mod input_history; From cacbf3d7a518c803d6849f8f5b6ea7ee62979bd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Merc=C3=AAs?= Date: Tue, 10 Mar 2026 02:55:40 -0300 Subject: [PATCH 02/13] Beep if Esc is pressed while in normal mode --- src/global/events.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/global/events.rs b/src/global/events.rs index 452c9e6..bd29621 100644 --- a/src/global/events.rs +++ b/src/global/events.rs @@ -1,11 +1,13 @@ use ratatui::crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; -use crate::{app::App, commands, editor::UIState, global}; +use crate::{app::App, beep, commands, editor::UIState, global}; use std::io::Result; pub fn handle_global_events(app: &mut App, key: KeyEvent) -> Result { match key.code { + // beep in NORMAL mode + KeyCode::Esc => beep!(), // switch views KeyCode::Tab => app.switch_editor_view(), // log window From f4c928b5ab256a22d6311cad75fbc39a61e452ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Merc=C3=AAs?= Date: Tue, 10 Mar 2026 02:55:51 -0300 Subject: [PATCH 03/13] Add new command to set the view --- src/commands.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/commands.rs b/src/commands.rs index c52ed70..49dd7bd 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -244,6 +244,26 @@ pub fn parse_command(app: &mut App, cmdline: &str) { app.config.search_wrap = false; app.dialog_renderer = None; } + // theme + "view" => { + if let Some(val) = value { + match val.as_str() { + "text" => { + app.editor_view = crate::editor::AppView::Text; + app.dialog_renderer = None; + } + "hex" => { + app.editor_view = crate::editor::AppView::Hex; + app.dialog_renderer = None; + } + "header" => { + app.editor_view = crate::editor::AppView::Header; + app.dialog_renderer = None; + } + _ => {} + } + } + } _ => { app.dialog_renderer = None; } From 92a7ba2bc70e674748624b5f5fe936988dfa729a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Merc=C3=AAs?= Date: Tue, 10 Mar 2026 02:56:22 -0300 Subject: [PATCH 04/13] Section table in Header view --- src/header/draw.rs | 57 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 47 insertions(+), 10 deletions(-) diff --git a/src/header/draw.rs b/src/header/draw.rs index af41e51..3668ea2 100644 --- a/src/header/draw.rs +++ b/src/header/draw.rs @@ -2,8 +2,9 @@ use std::fmt::{Display, LowerHex}; use ratatui::{ Frame, - layout::Rect, - widgets::{Block, Borders, Clear, List, ListItem}, + layout::{Constraint, Layout, Rect}, + style::Style, + widgets::{Block, Borders, Cell, Clear, List, ListItem, Row, Table}, }; use crate::app::App; @@ -43,8 +44,8 @@ fn header_contents_pe_draw(app: &mut App, frame: &mut Frame, area: Rect) -> erro //TODO check over/underflow app.header_view.entrypoint = ep_rva - sec.virtual_address + sec.pointer_to_raw_data; } - } - + } + add_to_list_hex( &mut items, "Entrypoint", @@ -63,23 +64,59 @@ fn header_contents_pe_draw(app: &mut App, frame: &mut Frame, area: Rect) -> erro ); } + let block = Block::new().borders(Borders::all()); + + // section table + + let mut rows = Vec::new(); + for sec in &pe.sections { + let mut cells = Vec::new(); if let Ok(name) = sec.name() { - items.push(ListItem::new(name.to_string())); + // items.push(ListItem::new(name.to_string())); + cells.push(Cell::new(name)); } - } + cells.push(Cell::new(sec.virtual_address.to_string())); + cells.push(Cell::new(sec.virtual_size.to_string())); + cells.push(Cell::new(sec.pointer_to_raw_data.to_string())); + cells.push(Cell::new(sec.size_of_raw_data.to_string())); + cells.push(Cell::new(sec.characteristics.to_string())); - for lib in pe.libraries { - items.push(ListItem::new(lib)); + rows.push(Row::new(cells)); } + let widths = [Constraint::Length(15); 6]; + let table = Table::new(rows, widths) + .column_spacing(1) + .style(app.config.theme.main) + .header( + Row::new(vec![ + "Name", + "VirtualAddress", + "VirtualSize", + "PtrToRawData", + "SizeOfRawData", + "Characteristics", + ]) + .style(Style::new().bold()), + ) + .row_highlight_style(Style::new().reversed()) + .column_highlight_style(Style::new().red()) + .cell_highlight_style(Style::new().blue()); + let list = List::new(items) .style(app.config.theme.main) .highlight_style(app.config.theme.highlight) - .block(Block::new().borders(Borders::all())); + .block(block); frame.render_widget(Clear, area); - frame.render_stateful_widget(list, area, &mut app.header_view.list_state); + + let layout = Layout::vertical([Constraint::Percentage(50), Constraint::Percentage(50)]); + let area1 = area.layout_vec(&layout); + + frame.render_stateful_widget(list, area1[0], &mut app.header_view.list_state); + + frame.render_widget(table, area1[1]); Ok(()) } From af25fc04eede4f69f6e36bdb556cbf531c8d2d19 Mon Sep 17 00:00:00 2001 From: merces Date: Fri, 27 Mar 2026 01:48:26 -0300 Subject: [PATCH 05/13] Basic ELF view --- src/app.rs | 123 ++++++++++++++++-- src/global/events.rs | 2 +- src/global/goto.rs | 7 +- src/header/draw.rs | 127 +----------------- src/header/events.rs | 25 +--- src/header/formats/elf/draw.rs | 214 +++++++++++++++++++++++++++++++ src/header/formats/elf/events.rs | 46 +++++++ src/header/formats/elf/mod.rs | 2 + src/header/formats/mod.rs | 2 + src/header/formats/pe/draw.rs | 163 +++++++++++++++++++++++ src/header/formats/pe/events.rs | 88 +++++++++++++ src/header/formats/pe/mod.rs | 2 + src/header/header_view.rs | 66 +++++++++- src/header/mod.rs | 1 + 14 files changed, 709 insertions(+), 159 deletions(-) create mode 100644 src/header/formats/elf/draw.rs create mode 100644 src/header/formats/elf/events.rs create mode 100644 src/header/formats/elf/mod.rs create mode 100644 src/header/formats/mod.rs create mode 100644 src/header/formats/pe/draw.rs create mode 100644 src/header/formats/pe/events.rs create mode 100644 src/header/formats/pe/mod.rs diff --git a/src/app.rs b/src/app.rs index 8820ada..0443274 100644 --- a/src/app.rs +++ b/src/app.rs @@ -6,16 +6,23 @@ use std::{ }; use arboard::Clipboard; -use goblin::Object; use goblin::error; +use goblin::{ + Object, + pe::header::{COFF_MACHINE_ARM, COFF_MACHINE_ARM64, COFF_MACHINE_X86, COFF_MACHINE_X86_64}, +}; use mmap_io::{MemoryMappedFile, MmapMode}; -use ratatui::{Frame, layout::Rect, widgets::ListState}; +use ratatui::{ + Frame, + layout::Rect, + widgets::{ListState, TableState}, +}; use crate::{ config::*, editor::*, global::calculator::Calculator, - header::header_view::HeaderView, + header::header_view::{Elf, HeaderView}, hex::{hex_view::HexView, strings::FoundString}, input_history::InputHistory, reader::Reader, @@ -111,7 +118,10 @@ impl App { dialog_2nd_renderer: None, editor_view: AppView::Hex, file_info: FileInfo::default(), - header_view: HeaderView::default(), + header_view: HeaderView { + // elf_header_table_state: TableState::new().with_selected_cell(Some((0, 1))), + ..Default::default() + }, hex_view: HexView { editing_hex: true, highlights: HashSet::with_capacity(8), @@ -144,20 +154,95 @@ impl App { self.file_info.r#type = match Object::parse(&buffer)? { Object::COFF(_coff) => "COFF", - Object::Elf(_elf) => "ELF", + Object::Elf(elf) => { + self.header_view.elf = Some(Elf { + header: elf.header.clone(), + phdrs: elf.program_headers.clone(), + header_table_state: TableState::default(), + }); + "ELF" + } Object::Mach(_mach) => "Mach-O", - Object::PE(_pe) => "PE", + Object::PE(pe) => { + let coff_header = crate::header::header_view::PECoffHeader { + characteristics: pe.header.coff_header.characteristics, + machine: pe.header.coff_header.machine, + time_date_stamp: pe.header.coff_header.time_date_stamp, + }; + + let optional_header = pe.header.optional_header.as_ref().map(|opt| { + crate::header::header_view::PEStandardFields { + address_of_entry_point: opt.standard_fields.address_of_entry_point, + magic: opt.standard_fields.magic, + minor_linker_version: opt.standard_fields.minor_linker_version, + major_linker_version: opt.standard_fields.major_linker_version, + } + }); + + let mut sections = Vec::new(); + for sec in &pe.sections { + sections.push(crate::header::header_view::PESection { + name: sec.name().unwrap_or_default().to_string(), + virtual_address: sec.virtual_address, + virtual_size: sec.virtual_size, + pointer_to_raw_data: sec.pointer_to_raw_data, + size_of_raw_data: sec.size_of_raw_data, + characteristics: sec.characteristics, + }); + } + let number_of_sections = sections.len(); + + // Calculate entrypoint + if let Some(opt) = pe.header.optional_header { + let ep_rva = opt.standard_fields.address_of_entry_point; + for sec in &pe.sections { + if ep_rva >= sec.virtual_address + && ep_rva < sec.virtual_address + sec.virtual_size + { + //TODO check over/underflow + self.header_view.entrypoint = + ep_rva - sec.virtual_address + sec.pointer_to_raw_data; + } + } + } + + let bitness = if pe.is_64 { + "PE32+ (64-bit)" + } else { + "PE32 (32-bit)" + }; + let kind = if pe.is_lib { "DLL" } else { "EXE" }; + let machine = get_pe_machine_string(pe.header.coff_header.machine); + + 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 number_of_imports = imports.len(); + self.header_view.pe = Some(crate::header::header_view::Pe { + summary: format!("{} {} {}", bitness, machine, kind), + coff_header, + optional_header, + sections, + imports, + number_of_sections, + number_of_imports, + }); + + "PE" + } Object::TE(_magic) => "TE", _ => "", }; - // self.file_info.r#type = match buffer[0] { - // 0x7f => "ELF", - // 0xca | 0xcf => "Mach-O", - // 0x4d => "PE", - // _ => "", - // }; - Ok(()) } @@ -217,7 +302,7 @@ impl App { let _ = self.load_database(); } - self.header_view.list_state.select_first(); + self.header_view.header_list_state.select_first(); Ok(()) } @@ -363,3 +448,13 @@ impl App { Some(i64::from_le_bytes([b1, b2, b3, b4, b5, b6, b7, b8])) } } + +fn get_pe_machine_string(mach: u16) -> &'static str { + match mach { + COFF_MACHINE_ARM => "ARM", + COFF_MACHINE_ARM64 => "AARCH64", + COFF_MACHINE_X86 => "x86", + COFF_MACHINE_X86_64 => "x86-64", + _ => "", + } +} diff --git a/src/global/events.rs b/src/global/events.rs index bd29621..8c82a5b 100644 --- a/src/global/events.rs +++ b/src/global/events.rs @@ -9,7 +9,7 @@ pub fn handle_global_events(app: &mut App, key: KeyEvent) -> Result { // beep in NORMAL mode KeyCode::Esc => beep!(), // switch views - KeyCode::Tab => app.switch_editor_view(), + KeyCode::Enter => app.switch_editor_view(), // log window KeyCode::Char('l') => { if key.modifiers.contains(KeyModifiers::ALT) { diff --git a/src/global/goto.rs b/src/global/goto.rs index 4d4ddd4..a3b9d53 100644 --- a/src/global/goto.rs +++ b/src/global/goto.rs @@ -1,5 +1,5 @@ use crate::app::App; -use crate::editor::UIState; +use crate::editor::{AppView, UIState}; impl App { /// The goto() function handles moving page position and smooth transition between pages @@ -68,6 +68,11 @@ impl App { // Update offset self.hex_view.offset = offset; + // if goto is called from Header view + if self.editor_view == AppView::Header { + self.editor_view = AppView::Hex; + } + App::log(self, format!("goto: {:x}", offset)); } } diff --git a/src/header/draw.rs b/src/header/draw.rs index 3668ea2..067033e 100644 --- a/src/header/draw.rs +++ b/src/header/draw.rs @@ -1,128 +1,11 @@ -use std::fmt::{Display, LowerHex}; +use ratatui::{Frame, layout::Rect}; -use ratatui::{ - Frame, - layout::{Constraint, Layout, Rect}, - style::Style, - widgets::{Block, Borders, Cell, Clear, List, ListItem, Row, Table}, -}; - -use crate::app::App; - -use goblin::error; -use goblin::pe::PE; - -fn add_to_list_hex(items: &mut Vec, label: &str, number: T) { - items.push(ListItem::new(format!("{:}: {:#x}", label, number))); -} - -fn add_to_list_dec(items: &mut Vec, label: &str, number: T) { - items.push(ListItem::new(format!("{:}: {:}", label, number))); -} - -fn header_contents_pe_draw(app: &mut App, frame: &mut Frame, area: Rect) -> error::Result<()> { - let buffer = app.file_info.get_buffer(); - let pe = PE::parse(&buffer)?; - let mut items = Vec::new(); - - add_to_list_hex( - &mut items, - "Characteristics", - pe.header.coff_header.characteristics, - ); - add_to_list_hex(&mut items, "Machine", pe.header.coff_header.machine); - add_to_list_hex( - &mut items, - "Compilation Timestamp", - pe.header.coff_header.time_date_stamp, - ); - - if let Some(opt) = pe.header.optional_header { - let ep_rva = opt.standard_fields.address_of_entry_point; - for sec in &pe.sections { - if ep_rva >= sec.virtual_address && ep_rva < sec.virtual_address + sec.virtual_size { - //TODO check over/underflow - app.header_view.entrypoint = ep_rva - sec.virtual_address + sec.pointer_to_raw_data; - } - } - - add_to_list_hex( - &mut items, - "Entrypoint", - opt.standard_fields.address_of_entry_point, - ); - add_to_list_hex(&mut items, "Magic", opt.standard_fields.magic); - add_to_list_dec( - &mut items, - "Minor linker version", - opt.standard_fields.minor_linker_version, - ); - add_to_list_dec( - &mut items, - "Major linker version", - opt.standard_fields.major_linker_version, - ); - } - - let block = Block::new().borders(Borders::all()); - - // section table - - let mut rows = Vec::new(); - - for sec in &pe.sections { - let mut cells = Vec::new(); - if let Ok(name) = sec.name() { - // items.push(ListItem::new(name.to_string())); - cells.push(Cell::new(name)); - } - cells.push(Cell::new(sec.virtual_address.to_string())); - cells.push(Cell::new(sec.virtual_size.to_string())); - cells.push(Cell::new(sec.pointer_to_raw_data.to_string())); - cells.push(Cell::new(sec.size_of_raw_data.to_string())); - cells.push(Cell::new(sec.characteristics.to_string())); - - rows.push(Row::new(cells)); - } - - let widths = [Constraint::Length(15); 6]; - let table = Table::new(rows, widths) - .column_spacing(1) - .style(app.config.theme.main) - .header( - Row::new(vec![ - "Name", - "VirtualAddress", - "VirtualSize", - "PtrToRawData", - "SizeOfRawData", - "Characteristics", - ]) - .style(Style::new().bold()), - ) - .row_highlight_style(Style::new().reversed()) - .column_highlight_style(Style::new().red()) - .cell_highlight_style(Style::new().blue()); - - let list = List::new(items) - .style(app.config.theme.main) - .highlight_style(app.config.theme.highlight) - .block(block); - - frame.render_widget(Clear, area); - - let layout = Layout::vertical([Constraint::Percentage(50), Constraint::Percentage(50)]); - let area1 = area.layout_vec(&layout); - - frame.render_stateful_widget(list, area1[0], &mut app.header_view.list_state); - - frame.render_widget(table, area1[1]); - - Ok(()) -} +use crate::{app::App, header::formats}; pub fn header_contents_draw(app: &mut App, frame: &mut Frame, area: Rect) { if app.file_info.r#type == "PE" { - let _ = header_contents_pe_draw(app, frame, area); + let _ = formats::pe::draw::pe_draw(app, frame, area); + } else if app.file_info.r#type == "ELF" { + let _ = formats::elf::draw::elf_draw(app, frame, area); } } diff --git a/src/header/events.rs b/src/header/events.rs index 4ff0d5e..c6f0432 100644 --- a/src/header/events.rs +++ b/src/header/events.rs @@ -1,26 +1,13 @@ use std::io::Result; -use ratatui::crossterm::event::{KeyCode, KeyEvent}; +use ratatui::crossterm::event::KeyEvent; -use crate::{app::App, editor::AppView}; +use crate::{app::App, header::formats}; pub fn header_view_events(app: &mut App, key: KeyEvent) -> Result { - match key.code { - KeyCode::Up | KeyCode::Char('k') => { - app.header_view.list_state.select_previous(); - } - KeyCode::Down | KeyCode::Char('j') => { - app.header_view.list_state.select_next(); - } - KeyCode::Enter => { - if let Some(idx) = app.header_view.list_state.selected() { - if idx == 3 { - app.goto(app.header_view.entrypoint as usize); - app.editor_view = AppView::Hex; - } - } - } - _ => {} + match app.file_info.r#type { + "ELF" => formats::elf::events::header_elf_view_events(app, key), + "PE" => formats::pe::events::header_pe_view_events(app, key), + _ => Ok(false), } - Ok(false) } diff --git a/src/header/formats/elf/draw.rs b/src/header/formats/elf/draw.rs new file mode 100644 index 0000000..800c76e --- /dev/null +++ b/src/header/formats/elf/draw.rs @@ -0,0 +1,214 @@ +use goblin::elf::{header::*, program_header::pt_to_str}; +use ratatui::{ + Frame, + layout::{Constraint, Layout, Rect}, + style::Style, + widgets::{Cell, Clear, Row, Table, Tabs}, +}; + +use crate::app::App; + +fn osabi_to_str(osabi: u8) -> &'static str { + // https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.eheader.html + match osabi { + ELFOSABI_NONE => "NONE", + ELFOSABI_HPUX => "HP-UX", + ELFOSABI_NETBSD => "NetBSD", + ELFOSABI_LINUX => "Linux", + ELFOSABI_SOLARIS => "Sun Solaris", + ELFOSABI_AIX => "AIX", + ELFOSABI_IRIX => "IRIX", + ELFOSABI_FREEBSD => "FreeBSD", + ELFOSABI_TRU64 => "Compaq TRU64 UNIX", + ELFOSABI_MODESTO => "Novell Modesto", + ELFOSABI_OPENBSD => "OpenBSD", + 13 => "OpenVMS", + 14 => "HP Non-Stop Kernel", + _ => "UNKNOWN_OSABI", + } +} + +fn draw_header(app: &mut App, frame: &mut Frame, area: Rect) { + if let Some(elf) = &app.header_view.elf { + let mut rows = Vec::new(); + + let e_ident = elf.header.e_ident; + + rows.push(Row::new(vec![ + Cell::new("Class"), + Cell::new(format!("{:08X} ({})", e_ident[4], class_to_str(e_ident[4]))), + ])); + + rows.push(Row::new(vec![ + Cell::new("Data"), + Cell::new(format!( + "{:08X} ({})", + e_ident[5], + match e_ident[5] { + 1 => "LSB/little endian", + 2 => "MSB/big endian", + _ => "Invalid data encoding", + } + )), + ])); + + rows.push(Row::new(vec![ + Cell::new("Version"), + Cell::new(format!( + "{:08X} ({})", + e_ident[6], + if e_ident[6] == 1 { + "current" + } else { + "invalid" + } + )), + ])); + + rows.push(Row::new(vec![ + Cell::new("OS/ABI"), + Cell::new(format!("{:08X} ({})", e_ident[7], osabi_to_str(e_ident[7]))), + ])); + + rows.push(Row::new(vec![ + Cell::new("Type"), + Cell::new(format!( + "{:08X} ({})", + elf.header.e_type, + et_to_str(elf.header.e_type) + )), + ])); + + rows.push(Row::new(vec![ + Cell::new("Machine"), + Cell::new(format!( + "{:08X} ({})", + elf.header.e_machine, + machine_to_str(elf.header.e_machine) + )), + ])); + + rows.push(Row::new(vec![ + Cell::new("Version"), + Cell::new(format!("{:08X}", elf.header.e_version)), + ])); + + rows.push(Row::new(vec![ + Cell::new("Entrypoint"), + Cell::new(format!("{:08X}", elf.header.e_entry)), + ])); + + rows.push(Row::new(vec![ + Cell::new("Program header offset"), + Cell::new(format!("{:08X}", elf.header.e_phoff)), + ])); + + rows.push(Row::new(vec![ + Cell::new("Section header offset"), + Cell::new(format!("{:08X}", elf.header.e_shoff)), + ])); + + rows.push(Row::new(vec![ + Cell::new("Flags"), + Cell::new(format!("{:08X}", elf.header.e_flags)), + ])); + + rows.push(Row::new(vec![ + Cell::new("(This) header size"), + Cell::new(format!("{:08X}", elf.header.e_ehsize)), + ])); + + rows.push(Row::new(vec![ + Cell::new("Program header size"), + Cell::new(format!("{:08X}", elf.header.e_phentsize)), + ])); + + rows.push(Row::new(vec![ + Cell::new("Number of program headers"), + Cell::new(format!("{:08X}", elf.header.e_phnum)), + ])); + + rows.push(Row::new(vec![ + Cell::new("Section headers size"), + Cell::new(format!("{:08X}", elf.header.e_shentsize)), + ])); + + rows.push(Row::new(vec![ + Cell::new("Number of section headers"), + Cell::new(format!("{:08X}", elf.header.e_shnum)), + ])); + + rows.push(Row::new(vec![ + Cell::new("Section header string table index"), + Cell::new(format!("{:08X}", elf.header.e_shstrndx)), + ])); + + let widths = [Constraint::Min(8), Constraint::Fill(1), Constraint::Fill(1)]; + + let header_table = Table::new(rows, widths) + .column_spacing(1) + .style(app.config.theme.main) + .cell_highlight_style(app.config.theme.highlight); + + frame.render_stateful_widget( + header_table, + area, + &mut app.header_view.elf_header_table_state, + ); + } +} + +fn draw_program_header(app: &mut App, frame: &mut Frame, area: Rect) { + if let Some(elf) = &app.header_view.elf { + let mut rows = Vec::new(); + + let phdrs = &elf.phdrs; + + for phdr in phdrs { + rows.push(Row::new(vec![ + Cell::new(format!("{}", pt_to_str(phdr.p_type))), + Cell::new(format!("{:08X}", phdr.p_offset)), + Cell::new(format!("{:08X}", phdr.p_filesz)), + Cell::new(format!("{:08X}", phdr.p_vaddr)), + Cell::new(format!("{:08X}", phdr.p_memsz)), + Cell::new(format!("{:08X}", phdr.p_paddr)), + Cell::new(format!("{:X}", phdr.p_flags)), + Cell::new(format!("{:X}", phdr.p_align)), + ])); + } + + let widths = [Constraint::Ratio(1, 8); 8]; + + let header_table = Table::new(rows, widths) + .column_spacing(1) + .style(app.config.theme.main) + .header(Row::new(vec![ + "Type", "Offset", "FileSiz", "VirtAddr", "MemSiz", "PhysAddr", "Flags", "Align", + ])) + .style(Style::new().bold()) + .cell_highlight_style(app.config.theme.highlight); + + frame.render_widget(header_table, area); + } +} + +pub fn elf_draw(app: &mut App, frame: &mut Frame, area: Rect) { + let tabs = Tabs::new(vec!["Header", "Program", "Sections", "Symbols"]) + .style(app.config.theme.main) + .highlight_style(app.config.theme.highlight) + .divider("|") + .padding(" ", " ") + .select(app.header_view.tab_index); + + let layout = Layout::vertical([Constraint::Length(1), Constraint::Fill(1)]); + let [top, main] = area.layout(&layout); + + frame.render_widget(Clear, area); + frame.render_widget(tabs, top); + + match app.header_view.tab_index { + 0 => draw_header(app, frame, main), + 1 => draw_program_header(app, frame, main), + _ => {} + } +} diff --git a/src/header/formats/elf/events.rs b/src/header/formats/elf/events.rs new file mode 100644 index 0000000..a49d5d4 --- /dev/null +++ b/src/header/formats/elf/events.rs @@ -0,0 +1,46 @@ +use std::io::Result; + +use ratatui::crossterm::event::{KeyCode, KeyEvent}; + +use crate::app::App; + +pub fn header_elf_view_events(app: &mut App, key: KeyEvent) -> Result { + match key.code { + KeyCode::Down | KeyCode::Char('j') => { + if app + .header_view + .elf_header_table_state + .selected_cell() + .is_none() + { + app.header_view + .elf_header_table_state + .select_cell(Some((0, 1))); + } else { + app.header_view.elf_header_table_state.select_next(); + } + } + KeyCode::Up | KeyCode::Char('k') => { + app.header_view.elf_header_table_state.select_previous(); + } + KeyCode::Tab => { + if app.header_view.tab_index < 3 { + app.header_view.tab_index = app.header_view.tab_index.saturating_add(1); + } else { + app.header_view.tab_index = 0; + } + } + KeyCode::BackTab => { + if app.header_view.tab_index > 0 { + app.header_view.tab_index = app.header_view.tab_index.saturating_sub(1); + } else { + app.header_view.tab_index = 3; + } + } + KeyCode::Char('G') => { + // goto + } + _ => {} + } + Ok(false) +} diff --git a/src/header/formats/elf/mod.rs b/src/header/formats/elf/mod.rs new file mode 100644 index 0000000..912b70d --- /dev/null +++ b/src/header/formats/elf/mod.rs @@ -0,0 +1,2 @@ +pub mod draw; +pub mod events; diff --git a/src/header/formats/mod.rs b/src/header/formats/mod.rs new file mode 100644 index 0000000..e4b79ca --- /dev/null +++ b/src/header/formats/mod.rs @@ -0,0 +1,2 @@ +pub mod elf; +pub mod pe; diff --git a/src/header/formats/pe/draw.rs b/src/header/formats/pe/draw.rs new file mode 100644 index 0000000..b258dac --- /dev/null +++ b/src/header/formats/pe/draw.rs @@ -0,0 +1,163 @@ +use ratatui::{ + Frame, + layout::{Alignment, Constraint, Layout, Rect}, + style::Style, + widgets::{Block, Borders, Cell, Clear, List, ListItem, Row, Table}, +}; + +use crate::app::App; + +pub fn pe_draw(app: &mut App, frame: &mut Frame, area: Rect) { + let pe_header = match app.header_view.pe.as_ref() { + Some(h) => h, + None => return, + }; + + // tabs + // let tabs = Tabs::new(vec!["DOS", "PE/COFF", "Dirs", "Sections", "Imports"]) + // .style(app.config.theme.main) + // .highlight_style(app.config.theme.highlight) + // .divider("|") + // .padding(" ", " "); + + let mut items = Vec::new(); + + items.push(ListItem::new(format!( + "{}: {:X}", + "Characteristics", pe_header.coff_header.characteristics + ))); + items.push(ListItem::new(format!( + "{}: {:X}", + "Machine", pe_header.coff_header.machine + ))); + items.push(ListItem::new(format!( + "{}: {}", + "Compilation Timestamp", pe_header.coff_header.time_date_stamp + ))); + + if let Some(opt) = &pe_header.optional_header { + items.push(ListItem::new(format!( + "{}: {:X}", + "Entrypoint", opt.address_of_entry_point + ))); + items.push(ListItem::new(format!("{}: {:X}", "Magic", opt.magic))); + items.push(ListItem::new(format!( + "{}: {}", + "Minor linker version", opt.minor_linker_version + ))); + items.push(ListItem::new(format!( + "{}: {}", + "Major linker version", opt.major_linker_version + ))); + } + + let headers_block = Block::new() + .borders(Borders::all()) + .title(pe_header.summary.clone()) + .title_alignment(Alignment::Center); + + let header_listing = List::new(items) + .style(app.config.theme.main) + .highlight_style(app.config.theme.highlight) + .block(headers_block); + + // section table + + let mut rows = Vec::new(); + + for sec in &pe_header.sections { + let mut cells = Vec::new(); + cells.push(Cell::new(sec.name.as_str())); + cells.push(Cell::new(format!("{:08X}", sec.virtual_address))); + cells.push(Cell::new(format!("{:08X}", sec.virtual_size))); + cells.push(Cell::new(format!("{:08X}", sec.pointer_to_raw_data))); + cells.push(Cell::new(format!("{:08X}", sec.size_of_raw_data))); + cells.push(Cell::new(format!("{:08X}", sec.characteristics))); + + rows.push(Row::new(cells)); + } + + let sections_block = Block::new() + .borders(Borders::all()) + .title("Sections") + .title_alignment(Alignment::Center); + + let widths = [Constraint::Ratio(1, 6); 6]; + + let section_table = Table::new(rows, widths) + .column_spacing(1) + .style(app.config.theme.main) + .header( + Row::new(vec![ + "Name", + "VirtualAddress", + "VirtualSize", + "PtrToRawData", + "SizeOfRawData", + "Characteristics", + ]) + .style(Style::new().bold()), + ) + .cell_highlight_style(app.config.theme.highlight) + .block(sections_block); + + // imports + let mut rows = Vec::new(); + + for imp in &pe_header.imports { + let mut cells = Vec::new(); + cells.push(Cell::new(imp.dll.as_str())); + cells.push(Cell::new(imp.name.as_str())); + cells.push(Cell::new(format!("{:08X}", imp.offset))); + cells.push(Cell::new(format!("{:08X}", imp.ordinal))); + cells.push(Cell::new(format!("{:08X}", imp.rva))); + // cells.push(Cell::new(format!("{:08X}", imp.size))); + + rows.push(Row::new(cells)); + } + + let imports_block = Block::new() + .borders(Borders::all()) + .title("Imports") + .title_alignment(Alignment::Center); + + let widths = [Constraint::Ratio(1, 5); 5]; + + let imports_table = Table::new(rows, widths) + .column_spacing(1) + .style(app.config.theme.main) + .header( + Row::new(vec![ + "DLL", "Name", "Offset", "Ordinal", "RVA", + // "Size", + ]) + .style(Style::new().bold()), + ) + .cell_highlight_style(app.config.theme.highlight) + .block(imports_block); + + let layout = Layout::vertical([ + Constraint::Length(9), + Constraint::Percentage(50), + Constraint::Percentage(50), + ]); + let sub_area = area.layout_vec(&layout); + + frame.render_widget(Clear, area); + // frame.render_widget(tabs, area); + frame.render_stateful_widget( + header_listing, + sub_area[0], + &mut app.header_view.header_list_state, + ); + frame.render_stateful_widget( + section_table, + sub_area[1], + &mut app.header_view.section_table_state, + ); + frame.render_stateful_widget( + imports_table, + sub_area[2], + &mut app.header_view.imports_table_state, + ); +} diff --git a/src/header/formats/pe/events.rs b/src/header/formats/pe/events.rs new file mode 100644 index 0000000..cac9958 --- /dev/null +++ b/src/header/formats/pe/events.rs @@ -0,0 +1,88 @@ +use std::io::Result; + +use ratatui::crossterm::event::{KeyCode, KeyEvent}; + +use crate::{app::App, editor::AppView}; + +pub fn header_pe_view_events(app: &mut App, key: KeyEvent) -> Result { + match key.code { + KeyCode::Up | KeyCode::Char('k') => { + if let Some(idx) = app.header_view.section_table_state.selected() { + if idx == 0 { + app.header_view.section_table_state.select(None); + app.header_view.section_table_state.select_column(None); + app.header_view.header_list_state.select_last(); + } else { + app.header_view.section_table_state.select_previous(); + } + } else if app.header_view.header_list_state.selected().is_some() { + app.header_view.header_list_state.select_previous(); + } else if let Some(idx) = app.header_view.imports_table_state.selected() { + if idx == 0 { + app.header_view.imports_table_state.select(None); + app.header_view.imports_table_state.select_column(None); + app.header_view.section_table_state.select_last(); + } else { + app.header_view.imports_table_state.select_previous(); + } + } + } + KeyCode::Down | KeyCode::Char('j') => { + if let Some(idx) = app.header_view.header_list_state.selected() { + if idx == 6 { + app.header_view.header_list_state.select(None); + app.header_view + .section_table_state + .select_cell(Some((0, 0))); + } else { + app.header_view.header_list_state.select_next(); + } + } else if let Some(idx) = app.header_view.section_table_state.selected() { + if idx + 1 == app.header_view.pe.as_ref().unwrap().number_of_sections { + app.header_view.section_table_state.select(None); + app.header_view + .imports_table_state + .select_cell(Some((0, 1))); + } else { + app.header_view.section_table_state.select_next(); + } + } else if app.header_view.imports_table_state.selected().is_some() { + app.header_view.imports_table_state.select_next(); + } + } + KeyCode::Left | KeyCode::Char('h') => { + if app.header_view.section_table_state.selected().is_some() { + app.header_view.section_table_state.select_previous_column(); + } + } + KeyCode::Right | KeyCode::Char('l') => { + if app.header_view.section_table_state.selected().is_some() { + app.header_view.section_table_state.select_next_column(); + } + } + KeyCode::Char('G') => { + if let Some(idx) = app.header_view.header_list_state.selected() { + if idx == 3 { + app.goto(app.header_view.entrypoint as usize); + app.editor_view = AppView::Hex; + } + } + } + KeyCode::Tab => { + if app.header_view.tab_index < 3 { + app.header_view.tab_index += 1; + } else { + app.header_view.tab_index = 0; + } + } + KeyCode::BackTab => { + if app.header_view.tab_index > 0 { + app.header_view.tab_index -= 1; + } else { + app.header_view.tab_index = 3; + } + } + _ => {} + } + Ok(false) +} diff --git a/src/header/formats/pe/mod.rs b/src/header/formats/pe/mod.rs new file mode 100644 index 0000000..912b70d --- /dev/null +++ b/src/header/formats/pe/mod.rs @@ -0,0 +1,2 @@ +pub mod draw; +pub mod events; diff --git a/src/header/header_view.rs b/src/header/header_view.rs index 60b3c99..2bd473f 100644 --- a/src/header/header_view.rs +++ b/src/header/header_view.rs @@ -1,7 +1,69 @@ -use ratatui::widgets::ListState; +use ratatui::widgets::{ListState, TableState}; + +#[derive(Debug, Clone)] +pub struct PECoffHeader { + pub characteristics: u16, + pub machine: u16, + pub time_date_stamp: u32, +} + +#[derive(Debug, Clone)] +pub struct PEStandardFields { + pub address_of_entry_point: u32, + pub magic: u16, + pub minor_linker_version: u8, + pub major_linker_version: u8, +} + +#[derive(Debug, Clone)] +pub struct PESection { + pub name: String, + pub virtual_address: u32, + pub virtual_size: u32, + pub pointer_to_raw_data: u32, + pub size_of_raw_data: u32, + pub characteristics: u32, +} + +#[derive(Debug, Clone)] +pub struct PEImport { + pub dll: String, + pub name: String, + pub offset: usize, + pub ordinal: u16, + pub rva: usize, + pub _size: usize, +} + +#[derive(Debug, Clone)] +pub struct Pe { + pub summary: String, + pub coff_header: PECoffHeader, + pub optional_header: Option, + pub sections: Vec, + pub number_of_sections: usize, + pub imports: Vec, + pub number_of_imports: usize, +} + +#[derive(Debug, Clone)] +pub struct Elf { + pub header: goblin::elf::Header, + pub phdrs: Vec, + pub header_table_state: TableState, + // pub program_header: Option, + // pub section_header: Vec, + // pub symbols: usize, +} #[derive(Default, Debug)] pub struct HeaderView { - pub list_state: ListState, + pub header_list_state: ListState, + pub section_table_state: TableState, + pub imports_table_state: TableState, pub entrypoint: u32, + pub pe: Option, + pub elf: Option, + pub elf_header_table_state: TableState, + pub tab_index: usize, } diff --git a/src/header/mod.rs b/src/header/mod.rs index ec3f867..4696537 100644 --- a/src/header/mod.rs +++ b/src/header/mod.rs @@ -1,3 +1,4 @@ pub mod draw; pub mod events; +pub mod formats; pub mod header_view; From 5de926e97359ecbd0a1c9ba5a3396abc3577a573 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Merc=C3=AAs?= Date: Sat, 28 Mar 2026 12:20:31 -0300 Subject: [PATCH 06/13] Fix command bar bug when initial view is not Hex --- src/draw.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/draw.rs b/src/draw.rs index 65ffc56..035159f 100644 --- a/src/draw.rs +++ b/src/draw.rs @@ -72,6 +72,8 @@ pub fn draw(frame: &mut Frame, app: &mut App) { // Draw status bar at the bottom global::status_bar::status_bar_draw(app, frame, vertical_layout[1]); + app.command_area = vertical_layout[2]; + let horizontal_layout = Layout::default() .direction(Direction::Horizontal) .constraints(vec![Constraint::Percentage(100)]) @@ -95,6 +97,8 @@ pub fn draw(frame: &mut Frame, app: &mut App) { // Draw status bar at the bottom global::status_bar::status_bar_draw(app, frame, vertical_layout[1]); + app.command_area = vertical_layout[2]; + let horizontal_layout = Layout::default() .direction(Direction::Horizontal) .constraints(vec![Constraint::Percentage(100)]) From d87fb1f99844e1fe36716e3d4f2711bd911373c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Merc=C3=AAs?= Date: Sun, 29 Mar 2026 20:55:20 -0300 Subject: [PATCH 07/13] ELF sections in Header view --- src/app.rs | 2 + src/commands.rs | 2 +- src/header/formats/elf/draw.rs | 99 +++++++++++++++++++++++++++++++- src/header/formats/elf/events.rs | 30 +++++++++- src/header/header_view.rs | 4 +- 5 files changed, 133 insertions(+), 4 deletions(-) diff --git a/src/app.rs b/src/app.rs index 0443274..20b94a7 100644 --- a/src/app.rs +++ b/src/app.rs @@ -158,6 +158,8 @@ impl App { self.header_view.elf = Some(Elf { header: elf.header.clone(), phdrs: elf.program_headers.clone(), + sections: elf.section_headers.clone(), + symtab: elf.syms.to_vec(), header_table_state: TableState::default(), }); "ELF" diff --git a/src/commands.rs b/src/commands.rs index 49dd7bd..76211c7 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -244,7 +244,7 @@ pub fn parse_command(app: &mut App, cmdline: &str) { app.config.search_wrap = false; app.dialog_renderer = None; } - // theme + // view "view" => { if let Some(val) = value { match val.as_str() { diff --git a/src/header/formats/elf/draw.rs b/src/header/formats/elf/draw.rs index 800c76e..c583566 100644 --- a/src/header/formats/elf/draw.rs +++ b/src/header/formats/elf/draw.rs @@ -1,4 +1,9 @@ -use goblin::elf::{header::*, program_header::pt_to_str}; +use goblin::elf::{ + header::*, + program_header::pt_to_str, + section_header::sht_to_str, + sym::{bind_to_str, type_to_str, visibility_to_str}, +}; use ratatui::{ Frame, layout::{Constraint, Layout, Rect}, @@ -192,6 +197,96 @@ fn draw_program_header(app: &mut App, frame: &mut Frame, area: Rect) { } } +fn draw_section_header(app: &mut App, frame: &mut Frame, area: Rect) { + if let Some(elf) = &app.header_view.elf { + let mut rows = Vec::new(); + + let strtab = elf.sections.get(elf.header.e_shstrndx as usize); + let buf = app.file_info.get_buffer(); + + for (i, section) in elf.sections.iter().enumerate() { + let mut name_cell = Cell::default(); + if let Some(strtab) = strtab { + let bytes: Vec = buf + .iter() + .skip(strtab.sh_offset as usize + section.sh_name) + .take_while(|b| **b != 0) + .copied() + .collect(); + + let name = String::from_utf8(bytes).unwrap_or_default(); + name_cell = Cell::new(name); + } + + rows.push(Row::new(vec![ + Cell::new(format!("{:X}", i)), + name_cell, + Cell::new(format!("{:08X}", section.sh_name)), + Cell::new(format!("{}", sht_to_str(section.sh_type))), + Cell::new(format!("{:08X}", section.sh_flags)), + Cell::new(format!("{:08X}", section.sh_addr)), + Cell::new(format!("{:08X}", section.sh_offset)), + Cell::new(format!("{:08X}", section.sh_size)), + Cell::new(format!("{:08X}", section.sh_link)), + Cell::new(format!("{:08X}", section.sh_info)), + Cell::new(format!("{:08X}", section.sh_addralign)), + Cell::new(format!("{:08X}", section.sh_entsize)), + ])); + } + + let widths = [Constraint::Ratio(1, 8); 8]; + + let header_table = Table::new(rows, widths) + .column_spacing(1) + .style(app.config.theme.main) + .header(Row::new(vec![ + "Idx", "Name", "NameIdx", "Type", "Flags", "Addr", "Offset", "Size", "Link", + "Info", "Align", "EntSize", + ])) + .style(Style::new().bold()) + .cell_highlight_style(app.config.theme.highlight); + + frame.render_widget(header_table, area); + } +} + +fn draw_symbols(app: &mut App, frame: &mut Frame, area: Rect) { + if let Some(elf) = &app.header_view.elf { + let mut rows = Vec::new(); + + for symbol in &elf.symtab { + rows.push(Row::new(vec![ + Cell::new(format!("{:X}", symbol.st_name)), + Cell::new(format!("{}", bind_to_str(symbol.st_bind()))), + Cell::new(format!("{}", type_to_str(symbol.st_type()))), + Cell::new(format!("{}", visibility_to_str(symbol.st_visibility()))), + Cell::new(format!("{:08X}", symbol.st_shndx)), + Cell::new(format!("{:08X}", symbol.st_value)), + Cell::new(format!("{:08X}", symbol.st_size)), + ])); + } + + let widths = [Constraint::Ratio(1, 8); 8]; + + let header_table = Table::new(rows, widths) + .column_spacing(1) + .style(app.config.theme.main) + .header(Row::new(vec![ + "Name", + "Bind", + "Type", + "Visibility", + "SecHdrIdx", + "Value", + "Size", + ])) + .style(Style::new().bold()) + .cell_highlight_style(app.config.theme.highlight); + + frame.render_widget(header_table, area); + } +} + pub fn elf_draw(app: &mut App, frame: &mut Frame, area: Rect) { let tabs = Tabs::new(vec!["Header", "Program", "Sections", "Symbols"]) .style(app.config.theme.main) @@ -209,6 +304,8 @@ pub fn elf_draw(app: &mut App, frame: &mut Frame, area: Rect) { match app.header_view.tab_index { 0 => draw_header(app, frame, main), 1 => draw_program_header(app, frame, main), + 2 => draw_section_header(app, frame, main), + 3 => draw_symbols(app, frame, main), _ => {} } } diff --git a/src/header/formats/elf/events.rs b/src/header/formats/elf/events.rs index a49d5d4..2751d10 100644 --- a/src/header/formats/elf/events.rs +++ b/src/header/formats/elf/events.rs @@ -4,6 +4,22 @@ use ratatui::crossterm::event::{KeyCode, KeyEvent}; use crate::app::App; +fn tab_next(app: &mut App) { + if app.header_view.tab_index < 3 { + app.header_view.tab_index = app.header_view.tab_index.saturating_add(1); + } else { + app.header_view.tab_index = 0; + } +} + +fn tab_prev(app: &mut App) { + if app.header_view.tab_index == 0 { + app.header_view.tab_index = 3; + } else { + app.header_view.tab_index -= 1; + } +} + pub fn header_elf_view_events(app: &mut App, key: KeyEvent) -> Result { match key.code { KeyCode::Down | KeyCode::Char('j') => { @@ -21,7 +37,19 @@ pub fn header_elf_view_events(app: &mut App, key: KeyEvent) -> Result { } } KeyCode::Up | KeyCode::Char('k') => { - app.header_view.elf_header_table_state.select_previous(); + if let Some(idx) = app.header_view.elf_header_table_state.selected() { + if idx == 0 { + app.header_view.elf_header_table_state.select(None); + } else { + app.header_view.elf_header_table_state.select_previous(); + } + } + } + KeyCode::Right | KeyCode::Char('l') => { + tab_next(app); + } + KeyCode::Left | KeyCode::Char('h') => { + tab_prev(app); } KeyCode::Tab => { if app.header_view.tab_index < 3 { diff --git a/src/header/header_view.rs b/src/header/header_view.rs index 2bd473f..9c7b4ea 100644 --- a/src/header/header_view.rs +++ b/src/header/header_view.rs @@ -49,7 +49,9 @@ pub struct Pe { #[derive(Debug, Clone)] pub struct Elf { pub header: goblin::elf::Header, - pub phdrs: Vec, + pub phdrs: goblin::elf::ProgramHeaders, + pub sections: goblin::elf::SectionHeaders, + pub symtab: Vec, pub header_table_state: TableState, // pub program_header: Option, // pub section_header: Vec, From e2cb58b9036c559b296424b080364c8567305729 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Merc=C3=AAs?= Date: Sun, 5 Apr 2026 18:48:20 -0300 Subject: [PATCH 08/13] Fix Cargo.lock after merge --- Cargo.lock | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index af21a0c..6282682 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1935,15 +1935,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -<<<<<<< HEAD -version = "0.2.114" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" -======= version = "0.2.115" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6523d69017b7633e396a89c5efab138161ed5aafcbc8d3e5c5a42ae38f50495a" ->>>>>>> main dependencies = [ "cfg-if", "once_cell", @@ -1954,15 +1948,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -<<<<<<< HEAD -version = "0.2.114" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" -======= version = "0.2.115" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e3a6c758eb2f701ed3d052ff5737f5bfe6614326ea7f3bbac7156192dc32e67" ->>>>>>> main dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1970,15 +1958,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -<<<<<<< HEAD -version = "0.2.114" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" -======= version = "0.2.115" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "921de2737904886b52bcbb237301552d05969a6f9c40d261eb0533c8b055fedf" ->>>>>>> main dependencies = [ "bumpalo", "proc-macro2", @@ -1989,15 +1971,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -<<<<<<< HEAD -version = "0.2.114" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" -======= version = "0.2.115" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a93e946af942b58934c604527337bad9ae33ba1d5c6900bbb41c2c07c2364a93" ->>>>>>> main dependencies = [ "unicode-ident", ] From d4634f6a9437f64f0bfe40d8a8ff13edbfe36faa Mon Sep 17 00:00:00 2001 From: merces Date: Sat, 16 May 2026 00:52:24 -0300 Subject: [PATCH 09/13] Remove capstone for now --- Cargo.lock | 42 ------------------------------------------ Cargo.toml | 1 - 2 files changed, 43 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6282682..6375a83 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -159,25 +159,6 @@ version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" -[[package]] -name = "capstone" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f442ae0f2f3f1b923334b4a5386c95c69c1cfa072bafa23d6fae6d9682eb1dd4" -dependencies = [ - "capstone-sys", - "static_assertions", -] - -[[package]] -name = "capstone-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e8087cab6731295f5a2a2bd82989ba4f41d3a428aab2e7c98d8f4db38aac05" -dependencies = [ - "cc", -] - [[package]] name = "castaway" version = "0.2.4" @@ -187,16 +168,6 @@ dependencies = [ "rustversion", ] -[[package]] -name = "cc" -version = "1.2.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" -dependencies = [ - "find-msvc-tools", - "shlex", -] - [[package]] name = "cfg-if" version = "1.0.4" @@ -469,7 +440,6 @@ name = "dz6" version = "0.6.0" dependencies = [ "arboard", - "capstone", "clap", "crossterm", "directories-next", @@ -560,12 +530,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "find-msvc-tools" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" - [[package]] name = "finl_unicode" version = "1.4.0" @@ -1552,12 +1516,6 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - [[package]] name = "signal-hook" version = "0.3.18" diff --git a/Cargo.toml b/Cargo.toml index 1d26dc3..22fa5cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,6 @@ categories = ["command-line-utilities", "security"] [dependencies] arboard = { version = "3", default-features = false } -capstone = "0.14.0" clap = { version = "4.5.49", features = ["derive"] } crossterm = "0.29.0" directories-next = "2.0.0" From 78218f8bc447b2cd9882569283d286e48e42f6ab Mon Sep 17 00:00:00 2001 From: merces Date: Sat, 16 May 2026 01:47:45 -0300 Subject: [PATCH 10/13] Add event handlers to all tabs --- src/app.rs | 9 +--- src/header/formats/elf/draw.rs | 17 ++++-- src/header/formats/elf/events.rs | 93 +++++++++++++++++++++++++++----- src/header/header_view.rs | 7 +-- 4 files changed, 96 insertions(+), 30 deletions(-) diff --git a/src/app.rs b/src/app.rs index 20b94a7..d9b22b3 100644 --- a/src/app.rs +++ b/src/app.rs @@ -12,11 +12,7 @@ use goblin::{ pe::header::{COFF_MACHINE_ARM, COFF_MACHINE_ARM64, COFF_MACHINE_X86, COFF_MACHINE_X86_64}, }; use mmap_io::{MemoryMappedFile, MmapMode}; -use ratatui::{ - Frame, - layout::Rect, - widgets::{ListState, TableState}, -}; +use ratatui::{Frame, layout::Rect, widgets::ListState}; use crate::{ config::*, @@ -160,7 +156,6 @@ impl App { phdrs: elf.program_headers.clone(), sections: elf.section_headers.clone(), symtab: elf.syms.to_vec(), - header_table_state: TableState::default(), }); "ELF" } @@ -228,7 +223,6 @@ impl App { }); } - let number_of_imports = imports.len(); self.header_view.pe = Some(crate::header::header_view::Pe { summary: format!("{} {} {}", bitness, machine, kind), coff_header, @@ -236,7 +230,6 @@ impl App { sections, imports, number_of_sections, - number_of_imports, }); "PE" diff --git a/src/header/formats/elf/draw.rs b/src/header/formats/elf/draw.rs index c583566..65531fc 100644 --- a/src/header/formats/elf/draw.rs +++ b/src/header/formats/elf/draw.rs @@ -191,9 +191,14 @@ fn draw_program_header(app: &mut App, frame: &mut Frame, area: Rect) { "Type", "Offset", "FileSiz", "VirtAddr", "MemSiz", "PhysAddr", "Flags", "Align", ])) .style(Style::new().bold()) + .style(app.config.theme.main) .cell_highlight_style(app.config.theme.highlight); - frame.render_widget(header_table, area); + frame.render_stateful_widget( + header_table, + area, + &mut app.header_view.elf_phrs_table_state, + ); } } @@ -244,9 +249,14 @@ fn draw_section_header(app: &mut App, frame: &mut Frame, area: Rect) { "Info", "Align", "EntSize", ])) .style(Style::new().bold()) + .style(app.config.theme.main) .cell_highlight_style(app.config.theme.highlight); - frame.render_widget(header_table, area); + frame.render_stateful_widget( + header_table, + area, + &mut app.header_view.elf_sections_table_state, + ); } } @@ -281,6 +291,7 @@ fn draw_symbols(app: &mut App, frame: &mut Frame, area: Rect) { "Size", ])) .style(Style::new().bold()) + .style(app.config.theme.main) .cell_highlight_style(app.config.theme.highlight); frame.render_widget(header_table, area); @@ -288,7 +299,7 @@ fn draw_symbols(app: &mut App, frame: &mut Frame, area: Rect) { } pub fn elf_draw(app: &mut App, frame: &mut Frame, area: Rect) { - let tabs = Tabs::new(vec!["Header", "Program", "Sections", "Symbols"]) + let tabs = Tabs::new(vec!["Header", "Phdrs", "Sections", "Symbols"]) .style(app.config.theme.main) .highlight_style(app.config.theme.highlight) .divider("|") diff --git a/src/header/formats/elf/events.rs b/src/header/formats/elf/events.rs index 2751d10..4b4b283 100644 --- a/src/header/formats/elf/events.rs +++ b/src/header/formats/elf/events.rs @@ -20,7 +20,7 @@ fn tab_prev(app: &mut App) { } } -pub fn header_elf_view_events(app: &mut App, key: KeyEvent) -> Result { +fn tab_header_events(app: &mut App, key: KeyEvent) -> Result { match key.code { KeyCode::Down | KeyCode::Char('j') => { if app @@ -45,24 +45,70 @@ pub fn header_elf_view_events(app: &mut App, key: KeyEvent) -> Result { } } } - KeyCode::Right | KeyCode::Char('l') => { - tab_next(app); - } - KeyCode::Left | KeyCode::Char('h') => { - tab_prev(app); + KeyCode::Char('G') => { + // goto } - KeyCode::Tab => { - if app.header_view.tab_index < 3 { - app.header_view.tab_index = app.header_view.tab_index.saturating_add(1); + _ => {} + } + Ok(false) +} + +fn tab_phdrs_events(app: &mut App, key: KeyEvent) -> Result { + match key.code { + KeyCode::Down | KeyCode::Char('j') => { + if app + .header_view + .elf_phrs_table_state + .selected_cell() + .is_none() + { + app.header_view + .elf_phrs_table_state + .select_cell(Some((0, 1))); } else { - app.header_view.tab_index = 0; + app.header_view.elf_phrs_table_state.select_next(); } } - KeyCode::BackTab => { - if app.header_view.tab_index > 0 { - app.header_view.tab_index = app.header_view.tab_index.saturating_sub(1); + KeyCode::Up | KeyCode::Char('k') => { + if let Some(idx) = app.header_view.elf_phrs_table_state.selected() { + if idx == 0 { + app.header_view.elf_phrs_table_state.select(None); + } else { + app.header_view.elf_phrs_table_state.select_previous(); + } + } + } + KeyCode::Char('G') => { + // goto + } + _ => {} + } + Ok(false) +} + +fn tab_sections_events(app: &mut App, key: KeyEvent) -> Result { + match key.code { + KeyCode::Down | KeyCode::Char('j') => { + if app + .header_view + .elf_sections_table_state + .selected_cell() + .is_none() + { + app.header_view + .elf_sections_table_state + .select_cell(Some((0, 1))); } else { - app.header_view.tab_index = 3; + app.header_view.elf_sections_table_state.select_next(); + } + } + KeyCode::Up | KeyCode::Char('k') => { + if let Some(idx) = app.header_view.elf_sections_table_state.selected() { + if idx == 0 { + app.header_view.elf_sections_table_state.select(None); + } else { + app.header_view.elf_sections_table_state.select_previous(); + } } } KeyCode::Char('G') => { @@ -72,3 +118,22 @@ pub fn header_elf_view_events(app: &mut App, key: KeyEvent) -> Result { } Ok(false) } + +pub fn header_elf_view_events(app: &mut App, key: KeyEvent) -> Result { + match key.code { + KeyCode::Tab | KeyCode::Right | KeyCode::Char('l') => { + tab_next(app); + } + KeyCode::BackTab | KeyCode::Left | KeyCode::Char('h') => { + tab_prev(app); + } + _ => (), + } + + match app.header_view.tab_index { + 0 => tab_header_events(app, key), + 1 => tab_phdrs_events(app, key), + 2 => tab_sections_events(app, key), + _ => Ok(false), + } +} diff --git a/src/header/header_view.rs b/src/header/header_view.rs index 9c7b4ea..05e45d6 100644 --- a/src/header/header_view.rs +++ b/src/header/header_view.rs @@ -43,7 +43,6 @@ pub struct Pe { pub sections: Vec, pub number_of_sections: usize, pub imports: Vec, - pub number_of_imports: usize, } #[derive(Debug, Clone)] @@ -52,10 +51,6 @@ pub struct Elf { pub phdrs: goblin::elf::ProgramHeaders, pub sections: goblin::elf::SectionHeaders, pub symtab: Vec, - pub header_table_state: TableState, - // pub program_header: Option, - // pub section_header: Vec, - // pub symbols: usize, } #[derive(Default, Debug)] @@ -67,5 +62,7 @@ pub struct HeaderView { pub pe: Option, pub elf: Option, pub elf_header_table_state: TableState, + pub elf_phrs_table_state: TableState, + pub elf_sections_table_state: TableState, pub tab_index: usize, } From cdd63bdd6fe8c57a8b4eb797f0a22dfbcb58b93d Mon Sep 17 00:00:00 2001 From: merces Date: Sat, 20 Jun 2026 20:41:33 -0300 Subject: [PATCH 11/13] Unify header view layout --- Cargo.lock | 516 ++++++++++++------------------- Cargo.toml | 2 +- src/app.rs | 82 +---- src/header/events.rs | 4 +- src/header/formats/elf/draw.rs | 18 +- src/header/formats/elf/events.rs | 119 +++++-- src/header/formats/pe/draw.rs | 443 +++++++++++++++++--------- src/header/formats/pe/events.rs | 220 +++++++++---- src/header/header_view.rs | 61 ++-- 9 files changed, 810 insertions(+), 655 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 27a2517..2da45f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,6 +73,15 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + [[package]] name = "arboard" version = "3.6.1" @@ -101,9 +110,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "base64" @@ -134,9 +143,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.11.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" [[package]] name = "block-buffer" @@ -149,9 +158,15 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.20.2" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "by_address" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" [[package]] name = "bytemuck" @@ -222,7 +237,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -248,9 +263,9 @@ checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "compact_str" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a" +checksum = "9dfdd1c2274d9aa354115b09dc9a901d6c5576818cdf70d14cae2bdb47df00ab" dependencies = [ "castaway", "cfg-if", @@ -287,13 +302,19 @@ dependencies = [ "libc", ] +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + [[package]] name = "crossterm" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "crossterm_winapi", "derive_more", "document-features", @@ -354,7 +375,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -365,7 +386,7 @@ checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ "darling_core", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -379,9 +400,6 @@ name = "deranged" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" -dependencies = [ - "powerfmt", -] [[package]] name = "derive_more" @@ -402,7 +420,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -442,7 +460,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "objc2", ] @@ -480,9 +498,9 @@ dependencies = [ [[package]] name = "either" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "encoding_rs" @@ -540,6 +558,12 @@ dependencies = [ "regex", ] +[[package]] +name = "fast-srgb8" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" + [[package]] name = "filedescriptor" version = "0.8.3" @@ -569,12 +593,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - [[package]] name = "foldhash" version = "0.2.0" @@ -650,38 +668,27 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", "libc", "r-efi 6.0.0", "rand_core 0.10.1", - "wasip2", - "wasip3", ] [[package]] name = "goblin" -version = "0.10.5" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "983a6aafb3b12d4c41ea78d39e189af4298ce747353945ff5105b54a056e5cd9" +checksum = "17582616a7718cca54cec18e534a76c7c4aec11a8b9a85695712f262fd15a4c8" dependencies = [ "log", "plain", "scroll", ] -[[package]] -name = "hashbrown" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "foldhash 0.1.5", -] - [[package]] name = "hashbrown" version = "0.16.1" @@ -690,7 +697,7 @@ checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ "allocator-api2", "equivalent", - "foldhash 0.2.0", + "foldhash", ] [[package]] @@ -698,6 +705,11 @@ name = "hashbrown" version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] [[package]] name = "heck" @@ -711,12 +723,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - [[package]] name = "ident_case" version = "1.0.1" @@ -731,8 +737,6 @@ checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", "hashbrown 0.17.1", - "serde", - "serde_core", ] [[package]] @@ -754,7 +758,7 @@ dependencies = [ "indoc", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -780,13 +784,12 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "js-sys" -version = "0.3.98" +version = "0.3.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" +checksum = "03d04c30968dffe80775bd4d7fb676131cd04a1fb46d2686dbffbaec2d9dfd31" dependencies = [ "cfg-if", "futures-util", - "once_cell", "wasm-bindgen", ] @@ -813,23 +816,23 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" - [[package]] name = "libc" version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + [[package]] name = "libredox" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" dependencies = [ "libc", ] @@ -840,7 +843,7 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f50e8f47623268b5407192d26876c4d7f89d686ca130fdc53bced4814cd29f8" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", ] [[package]] @@ -866,17 +869,17 @@ dependencies = [ [[package]] name = "log" -version = "0.4.29" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] name = "lru" -version = "0.16.4" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39" +checksum = "8a860605968fce16869fd239cf4237a82f3ac470723415db603b0e8b6c8d4fb9" dependencies = [ - "hashbrown 0.16.1", + "hashbrown 0.17.1", ] [[package]] @@ -891,9 +894,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" [[package]] name = "memmap2" @@ -927,9 +930,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "mio" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" dependencies = [ "libc", "log", @@ -939,9 +942,9 @@ dependencies = [ [[package]] name = "mmap-io" -version = "0.9.4" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a34b926fd173419d630eb9d8ee3a485345237bf699d6f7ea6e5c1ee97cf1fa47" +checksum = "da7573a60d8c5fa36a94411413740fcf2ef1fc0b1f21700b57d4abbcd3bf7ff9" dependencies = [ "anyhow", "cfg-if", @@ -958,7 +961,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "cfg-if", "cfg_aliases", "libc", @@ -977,9 +980,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-derive" @@ -989,7 +992,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1025,7 +1028,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "objc2", "objc2-core-graphics", "objc2-foundation", @@ -1037,7 +1040,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "dispatch2", "objc2", ] @@ -1048,7 +1051,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "dispatch2", "objc2", "objc2-core-foundation", @@ -1067,7 +1070,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "objc2", "objc2-core-foundation", ] @@ -1078,7 +1081,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "objc2", "objc2-core-foundation", ] @@ -1104,6 +1107,30 @@ dependencies = [ "num-traits", ] +[[package]] +name = "palette" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cbf71184cc5ecc2e4e1baccdb21026c20e5fc3dcf63028a086131b3ab00b6e6" +dependencies = [ + "approx", + "fast-srgb8", + "libm", + "palette_derive", +] + +[[package]] +name = "palette_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5030daf005bface118c096f510ffb781fc28f9ab6a32ab224d8631be6851d30" +dependencies = [ + "by_address", + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "parking_lot" version = "0.12.5" @@ -1163,7 +1190,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1216,7 +1243,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1252,16 +1279,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" -[[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn 2.0.117", -] - [[package]] name = "proc-macro2" version = "1.0.106" @@ -1308,7 +1325,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" dependencies = [ "chacha20", - "getrandom 0.4.2", + "getrandom 0.4.3", "rand_core 0.10.1", ] @@ -1326,31 +1343,35 @@ checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" [[package]] name = "ratatui" -version = "0.30.0" +version = "0.30.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1ce67fb8ba4446454d1c8dbaeda0557ff5e94d39d5e5ed7f10a65eb4c8266bc" +checksum = "3274ba0a2c5e1bcad2a2005d20f4dc59dad26b2eb0940fb094500dba4099d57d" dependencies = [ "instability", "ratatui-core", "ratatui-crossterm", "ratatui-macros", + "ratatui-termina", "ratatui-termwiz", "ratatui-widgets", + "serde", ] [[package]] name = "ratatui-core" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef8dea09a92caaf73bff7adb70b76162e5937524058a7e5bff37869cbbec293" +checksum = "cbb175c433c8e28a809d1f5773a2ae96e68c0ce40db865cbab1020bf33ae479c" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "compact_str", - "hashbrown 0.16.1", - "indoc", + "critical-section", + "hashbrown 0.17.1", "itertools", "kasuari", "lru", + "palette", + "serde", "strum", "thiserror 2.0.18", "unicode-segmentation", @@ -1360,9 +1381,9 @@ dependencies = [ [[package]] name = "ratatui-crossterm" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "577c9b9f652b4c121fb25c6a391dd06406d3b092ba68827e6d2f09550edc54b3" +checksum = "567584a3b0e6a8203c23de40b4861497266725eb5363dbfd18a1edd603cca9f0" dependencies = [ "cfg-if", "crossterm", @@ -1372,19 +1393,30 @@ dependencies = [ [[package]] name = "ratatui-macros" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7f1342a13e83e4bb9d0b793d0ea762be633f9582048c892ae9041ef39c936f4" +checksum = "ed7dc68daa7498a43e4d68e0eb078427e10c38fbcfbb1e42d955f1fa2140d814" dependencies = [ "ratatui-core", "ratatui-widgets", ] [[package]] -name = "ratatui-termwiz" +name = "ratatui-termina" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f76fe0bd0ed4295f0321b1676732e2454024c15a35d01904ddb315afd3d545c" +checksum = "c0bf912d9e66f057a759d92e386a280ea886b352ab757d6ac4d653c7ed2c43c2" +dependencies = [ + "instability", + "ratatui-core", + "termina", +] + +[[package]] +name = "ratatui-termwiz" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf03e0380b7744054d6cb74224fe3adf062a029754933f575ca1e3b4c2ce977" dependencies = [ "ratatui-core", "termwiz", @@ -1392,17 +1424,18 @@ dependencies = [ [[package]] name = "ratatui-widgets" -version = "0.3.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7dbfa023cd4e604c2553483820c5fe8aa9d71a42eea5aa77c6e7f35756612db" +checksum = "66e3d19bcc9130ca376277d93b60767ff121ace3be06f5f95f81dd68956407d1" dependencies = [ - "bitflags 2.11.1", - "hashbrown 0.16.1", + "bitflags 2.13.0", + "hashbrown 0.17.1", "indoc", "instability", "itertools", "line-clipping", "ratatui-core", + "serde", "strum", "time", "unicode-segmentation", @@ -1415,7 +1448,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", ] [[package]] @@ -1431,9 +1464,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.3" +version = "1.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" dependencies = [ "aho-corasick", "memchr", @@ -1454,9 +1487,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "rustc_version" @@ -1473,7 +1506,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "errno", "libc", "linux-raw-sys", @@ -1515,7 +1548,7 @@ checksum = "ed76efe62313ab6610570951494bdaa81568026e0318eaa55f167de70eeea67d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1551,20 +1584,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", -] - -[[package]] -name = "serde_json" -version = "1.0.149" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", + "syn 2.0.118", ] [[package]] @@ -1638,9 +1658,9 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.15.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] name = "static_assertions" @@ -1656,23 +1676,23 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" -version = "0.27.2" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" dependencies = [ "strum_macros", ] [[package]] name = "strum_macros" -version = "0.27.2" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1688,15 +1708,28 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.117" +version = "2.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "termina" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9048a889effe34a5cddee0af7f53285198b16dca3be510858d38dfdb3e62a04e" +dependencies = [ + "bitflags 2.13.0", + "parking_lot", + "rustix", + "signal-hook", + "windows-sys 0.61.2", +] + [[package]] name = "terminfo" version = "0.9.0" @@ -1726,7 +1759,7 @@ checksum = "4676b37242ccbd1aabf56edb093a4827dc49086c0ffd764a5705899e0f35f8f7" dependencies = [ "anyhow", "base64", - "bitflags 2.11.1", + "bitflags 2.13.0", "fancy-regex", "filedescriptor", "finl_unicode", @@ -1786,7 +1819,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] @@ -1797,14 +1830,14 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", ] [[package]] name = "time" -version = "0.3.47" +version = "0.3.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +checksum = "711a53c2d47bbd818258c498c8dbfe186a2526c631495cfe7e078567f86b8469" dependencies = [ "deranged", "libc", @@ -1817,9 +1850,9 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "toml" @@ -1873,9 +1906,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.20.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "ucd-trie" @@ -1891,9 +1924,9 @@ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" -version = "1.13.2" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" [[package]] name = "unicode-truncate" @@ -1912,12 +1945,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - [[package]] name = "utf8parse" version = "0.2.2" @@ -1926,12 +1953,12 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.23.1" +version = "1.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +checksum = "144d6b123cef80b301b8f72a9e2ca4370ddec21950d0a103dd22c437006d2db7" dependencies = [ "atomic", - "getrandom 0.4.2", + "getrandom 0.4.3", "js-sys", "wasm-bindgen", ] @@ -1959,27 +1986,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.3+wasi-0.2.9" +version = "1.0.4+wasi-0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" dependencies = [ - "wit-bindgen 0.57.1", -] - -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" -dependencies = [ - "wit-bindgen 0.51.0", + "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.121" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" +checksum = "8ddb3f79143bced6de84270411622a2699cee572fc0875aeaf1e7867cf9fca1a" dependencies = [ "cfg-if", "once_cell", @@ -1990,9 +2008,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.121" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" +checksum = "4e21a184b13fb19e157296e2c46056aec9092264fab83e4ba59e68c61b323c3d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2000,60 +2018,26 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.121" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" +checksum = "fecefd9c35bd935a20fc3fc344b5f29138961e4f47fb03297d88f2587afb5ebd" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.118", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.121" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" +checksum = "23939e44bb9a5d7576fa2b563dc2e136628f1224e88a8deed09e04858b77871f" dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap", - "wasm-encoder", - "wasmparser", -] - -[[package]] -name = "wasmparser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" -dependencies = [ - "bitflags 2.11.1", - "hashbrown 0.15.5", - "indexmap", - "semver", -] - [[package]] name = "wezterm-bidi" version = "0.2.3" @@ -2239,18 +2223,9 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" - -[[package]] -name = "wit-bindgen" -version = "0.51.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" [[package]] name = "wit-bindgen" @@ -2258,85 +2233,6 @@ version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck", - "indexmap", - "prettyplease", - "syn 2.0.117", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn 2.0.117", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags 2.11.1", - "indexmap", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", -] - [[package]] name = "x11rb" version = "0.13.2" @@ -2353,9 +2249,3 @@ name = "x11rb-protocol" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" - -[[package]] -name = "zmij" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index 295e8ac..29e3783 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ evalexpr = "13.1.*" goblin = "0.10.5" hex = "0.4.3" memchr = "2.8.0" -mmap-io = "0.9.4" +mmap-io = "1.0.0" rand = "0.10.1" ratatui = "0.30.0" regex = "1.11.2" diff --git a/src/app.rs b/src/app.rs index d9b22b3..62cee6e 100644 --- a/src/app.rs +++ b/src/app.rs @@ -6,11 +6,8 @@ use std::{ }; use arboard::Clipboard; +use goblin::Object; use goblin::error; -use goblin::{ - Object, - pe::header::{COFF_MACHINE_ARM, COFF_MACHINE_ARM64, COFF_MACHINE_X86, COFF_MACHINE_X86_64}, -}; use mmap_io::{MemoryMappedFile, MmapMode}; use ratatui::{Frame, layout::Rect, widgets::ListState}; @@ -18,7 +15,7 @@ use crate::{ config::*, editor::*, global::calculator::Calculator, - header::header_view::{Elf, HeaderView}, + header::header_view::{Elf, HeaderView, Pe}, hex::{hex_view::HexView, strings::FoundString}, input_history::InputHistory, reader::Reader, @@ -44,7 +41,7 @@ impl FileInfo { /// which also takes care of unloading it if memory constrained. pub fn get_buffer(&mut self) -> &[u8] { if let Some(mmap) = self.mmap.as_mut() { - return mmap.as_slice(0, self.size as u64).unwrap(); + return mmap.as_slice_bytes(0, self.size as u64).unwrap(); } &[] @@ -161,56 +158,6 @@ impl App { } Object::Mach(_mach) => "Mach-O", Object::PE(pe) => { - let coff_header = crate::header::header_view::PECoffHeader { - characteristics: pe.header.coff_header.characteristics, - machine: pe.header.coff_header.machine, - time_date_stamp: pe.header.coff_header.time_date_stamp, - }; - - let optional_header = pe.header.optional_header.as_ref().map(|opt| { - crate::header::header_view::PEStandardFields { - address_of_entry_point: opt.standard_fields.address_of_entry_point, - magic: opt.standard_fields.magic, - minor_linker_version: opt.standard_fields.minor_linker_version, - major_linker_version: opt.standard_fields.major_linker_version, - } - }); - - let mut sections = Vec::new(); - for sec in &pe.sections { - sections.push(crate::header::header_view::PESection { - name: sec.name().unwrap_or_default().to_string(), - virtual_address: sec.virtual_address, - virtual_size: sec.virtual_size, - pointer_to_raw_data: sec.pointer_to_raw_data, - size_of_raw_data: sec.size_of_raw_data, - characteristics: sec.characteristics, - }); - } - let number_of_sections = sections.len(); - - // Calculate entrypoint - if let Some(opt) = pe.header.optional_header { - let ep_rva = opt.standard_fields.address_of_entry_point; - for sec in &pe.sections { - if ep_rva >= sec.virtual_address - && ep_rva < sec.virtual_address + sec.virtual_size - { - //TODO check over/underflow - self.header_view.entrypoint = - ep_rva - sec.virtual_address + sec.pointer_to_raw_data; - } - } - } - - let bitness = if pe.is_64 { - "PE32+ (64-bit)" - } else { - "PE32 (32-bit)" - }; - let kind = if pe.is_lib { "DLL" } else { "EXE" }; - let machine = get_pe_machine_string(pe.header.coff_header.machine); - let mut imports = Vec::new(); for imp in pe.imports { imports.push(crate::header::header_view::PEImport { @@ -223,13 +170,12 @@ impl App { }); } - self.header_view.pe = Some(crate::header::header_view::Pe { - summary: format!("{} {} {}", bitness, machine, kind), - coff_header, - optional_header, - sections, + self.header_view.pe = Some(Pe { + dos_header: pe.header.dos_header.clone(), + coff_header: pe.header.coff_header.clone(), + optional_header: pe.header.optional_header.clone(), + sections: pe.sections, imports, - number_of_sections, }); "PE" @@ -297,8 +243,6 @@ impl App { let _ = self.load_database(); } - self.header_view.header_list_state.select_first(); - Ok(()) } @@ -443,13 +387,3 @@ impl App { Some(i64::from_le_bytes([b1, b2, b3, b4, b5, b6, b7, b8])) } } - -fn get_pe_machine_string(mach: u16) -> &'static str { - match mach { - COFF_MACHINE_ARM => "ARM", - COFF_MACHINE_ARM64 => "AARCH64", - COFF_MACHINE_X86 => "x86", - COFF_MACHINE_X86_64 => "x86-64", - _ => "", - } -} diff --git a/src/header/events.rs b/src/header/events.rs index c6f0432..e7dd37a 100644 --- a/src/header/events.rs +++ b/src/header/events.rs @@ -6,8 +6,8 @@ use crate::{app::App, header::formats}; pub fn header_view_events(app: &mut App, key: KeyEvent) -> Result { match app.file_info.r#type { - "ELF" => formats::elf::events::header_elf_view_events(app, key), - "PE" => formats::pe::events::header_pe_view_events(app, key), + "ELF" => formats::elf::events::view_header_elf_events(app, key), + "PE" => formats::pe::events::view_header_pe_events(app, key), _ => Ok(false), } } diff --git a/src/header/formats/elf/draw.rs b/src/header/formats/elf/draw.rs index 65531fc..453845e 100644 --- a/src/header/formats/elf/draw.rs +++ b/src/header/formats/elf/draw.rs @@ -33,7 +33,7 @@ fn osabi_to_str(osabi: u8) -> &'static str { } } -fn draw_header(app: &mut App, frame: &mut Frame, area: Rect) { +fn draw_elf_header(app: &mut App, frame: &mut Frame, area: Rect) { if let Some(elf) = &app.header_view.elf { let mut rows = Vec::new(); @@ -158,7 +158,7 @@ fn draw_header(app: &mut App, frame: &mut Frame, area: Rect) { frame.render_stateful_widget( header_table, area, - &mut app.header_view.elf_header_table_state, + &mut app.header_view.elf_state.elf_header_table_state, ); } } @@ -197,7 +197,7 @@ fn draw_program_header(app: &mut App, frame: &mut Frame, area: Rect) { frame.render_stateful_widget( header_table, area, - &mut app.header_view.elf_phrs_table_state, + &mut app.header_view.elf_state.program_header_table_state, ); } } @@ -255,7 +255,7 @@ fn draw_section_header(app: &mut App, frame: &mut Frame, area: Rect) { frame.render_stateful_widget( header_table, area, - &mut app.header_view.elf_sections_table_state, + &mut app.header_view.elf_state.sections_table_state, ); } } @@ -278,7 +278,7 @@ fn draw_symbols(app: &mut App, frame: &mut Frame, area: Rect) { let widths = [Constraint::Ratio(1, 8); 8]; - let header_table = Table::new(rows, widths) + let symbol_table = Table::new(rows, widths) .column_spacing(1) .style(app.config.theme.main) .header(Row::new(vec![ @@ -294,7 +294,11 @@ fn draw_symbols(app: &mut App, frame: &mut Frame, area: Rect) { .style(app.config.theme.main) .cell_highlight_style(app.config.theme.highlight); - frame.render_widget(header_table, area); + frame.render_stateful_widget( + symbol_table, + area, + &mut app.header_view.elf_state.symbols_table_state, + ); } } @@ -313,7 +317,7 @@ pub fn elf_draw(app: &mut App, frame: &mut Frame, area: Rect) { frame.render_widget(tabs, top); match app.header_view.tab_index { - 0 => draw_header(app, frame, main), + 0 => draw_elf_header(app, frame, main), 1 => draw_program_header(app, frame, main), 2 => draw_section_header(app, frame, main), 3 => draw_symbols(app, frame, main), diff --git a/src/header/formats/elf/events.rs b/src/header/formats/elf/events.rs index 4b4b283..fc85314 100644 --- a/src/header/formats/elf/events.rs +++ b/src/header/formats/elf/events.rs @@ -4,8 +4,10 @@ use ratatui::crossterm::event::{KeyCode, KeyEvent}; use crate::app::App; +const NUMBER_OF_TABS: usize = 4; + fn tab_next(app: &mut App) { - if app.header_view.tab_index < 3 { + if app.header_view.tab_index < NUMBER_OF_TABS - 1 { app.header_view.tab_index = app.header_view.tab_index.saturating_add(1); } else { app.header_view.tab_index = 0; @@ -14,34 +16,45 @@ fn tab_next(app: &mut App) { fn tab_prev(app: &mut App) { if app.header_view.tab_index == 0 { - app.header_view.tab_index = 3; + app.header_view.tab_index = NUMBER_OF_TABS - 1; } else { app.header_view.tab_index -= 1; } } -fn tab_header_events(app: &mut App, key: KeyEvent) -> Result { +fn tab_elf_header_events(app: &mut App, key: KeyEvent) -> Result { match key.code { KeyCode::Down | KeyCode::Char('j') => { if app .header_view + .elf_state .elf_header_table_state .selected_cell() .is_none() { app.header_view + .elf_state .elf_header_table_state .select_cell(Some((0, 1))); } else { - app.header_view.elf_header_table_state.select_next(); + app.header_view + .elf_state + .elf_header_table_state + .select_next(); } } KeyCode::Up | KeyCode::Char('k') => { - if let Some(idx) = app.header_view.elf_header_table_state.selected() { + if let Some(idx) = app.header_view.elf_state.elf_header_table_state.selected() { if idx == 0 { - app.header_view.elf_header_table_state.select(None); + app.header_view + .elf_state + .elf_header_table_state + .select(None); } else { - app.header_view.elf_header_table_state.select_previous(); + app.header_view + .elf_state + .elf_header_table_state + .select_previous(); } } } @@ -53,28 +66,44 @@ fn tab_header_events(app: &mut App, key: KeyEvent) -> Result { Ok(false) } -fn tab_phdrs_events(app: &mut App, key: KeyEvent) -> Result { +fn tab_program_headers_events(app: &mut App, key: KeyEvent) -> Result { match key.code { KeyCode::Down | KeyCode::Char('j') => { if app .header_view - .elf_phrs_table_state + .elf_state + .program_header_table_state .selected_cell() .is_none() { app.header_view - .elf_phrs_table_state + .elf_state + .program_header_table_state .select_cell(Some((0, 1))); } else { - app.header_view.elf_phrs_table_state.select_next(); + app.header_view + .elf_state + .program_header_table_state + .select_next(); } } KeyCode::Up | KeyCode::Char('k') => { - if let Some(idx) = app.header_view.elf_phrs_table_state.selected() { + if let Some(idx) = app + .header_view + .elf_state + .program_header_table_state + .selected() + { if idx == 0 { - app.header_view.elf_phrs_table_state.select(None); + app.header_view + .elf_state + .program_header_table_state + .select(None); } else { - app.header_view.elf_phrs_table_state.select_previous(); + app.header_view + .elf_state + .program_header_table_state + .select_previous(); } } } @@ -91,23 +120,66 @@ fn tab_sections_events(app: &mut App, key: KeyEvent) -> Result { KeyCode::Down | KeyCode::Char('j') => { if app .header_view - .elf_sections_table_state + .elf_state + .sections_table_state .selected_cell() .is_none() { app.header_view - .elf_sections_table_state + .elf_state + .sections_table_state .select_cell(Some((0, 1))); } else { - app.header_view.elf_sections_table_state.select_next(); + app.header_view.elf_state.sections_table_state.select_next(); + } + } + KeyCode::Up | KeyCode::Char('k') => { + if let Some(idx) = app.header_view.elf_state.sections_table_state.selected() { + if idx == 0 { + app.header_view.elf_state.sections_table_state.select(None); + } else { + app.header_view + .elf_state + .sections_table_state + .select_previous(); + } + } + } + KeyCode::Char('G') => { + // goto + } + _ => {} + } + Ok(false) +} + +fn tab_symbols_events(app: &mut App, key: KeyEvent) -> Result { + match key.code { + KeyCode::Down | KeyCode::Char('j') => { + if app + .header_view + .elf_state + .symbols_table_state + .selected_cell() + .is_none() + { + app.header_view + .elf_state + .symbols_table_state + .select_cell(Some((0, 0))); + } else { + app.header_view.elf_state.symbols_table_state.select_next(); } } KeyCode::Up | KeyCode::Char('k') => { - if let Some(idx) = app.header_view.elf_sections_table_state.selected() { + if let Some(idx) = app.header_view.elf_state.symbols_table_state.selected() { if idx == 0 { - app.header_view.elf_sections_table_state.select(None); + app.header_view.elf_state.symbols_table_state.select(None); } else { - app.header_view.elf_sections_table_state.select_previous(); + app.header_view + .elf_state + .symbols_table_state + .select_previous(); } } } @@ -119,7 +191,7 @@ fn tab_sections_events(app: &mut App, key: KeyEvent) -> Result { Ok(false) } -pub fn header_elf_view_events(app: &mut App, key: KeyEvent) -> Result { +pub fn view_header_elf_events(app: &mut App, key: KeyEvent) -> Result { match key.code { KeyCode::Tab | KeyCode::Right | KeyCode::Char('l') => { tab_next(app); @@ -131,9 +203,10 @@ pub fn header_elf_view_events(app: &mut App, key: KeyEvent) -> Result { } match app.header_view.tab_index { - 0 => tab_header_events(app, key), - 1 => tab_phdrs_events(app, key), + 0 => tab_elf_header_events(app, key), + 1 => tab_program_headers_events(app, key), 2 => tab_sections_events(app, key), + 3 => tab_symbols_events(app, key), _ => Ok(false), } } diff --git a/src/header/formats/pe/draw.rs b/src/header/formats/pe/draw.rs index b258dac..8c4db98 100644 --- a/src/header/formats/pe/draw.rs +++ b/src/header/formats/pe/draw.rs @@ -1,163 +1,322 @@ use ratatui::{ Frame, - layout::{Alignment, Constraint, Layout, Rect}, + layout::{Constraint, Layout, Rect}, style::Style, - widgets::{Block, Borders, Cell, Clear, List, ListItem, Row, Table}, + widgets::{Cell, Clear, Row, Table, Tabs}, }; use crate::app::App; -pub fn pe_draw(app: &mut App, frame: &mut Frame, area: Rect) { - let pe_header = match app.header_view.pe.as_ref() { - Some(h) => h, - None => return, - }; - - // tabs - // let tabs = Tabs::new(vec!["DOS", "PE/COFF", "Dirs", "Sections", "Imports"]) - // .style(app.config.theme.main) - // .highlight_style(app.config.theme.highlight) - // .divider("|") - // .padding(" ", " "); - - let mut items = Vec::new(); - - items.push(ListItem::new(format!( - "{}: {:X}", - "Characteristics", pe_header.coff_header.characteristics - ))); - items.push(ListItem::new(format!( - "{}: {:X}", - "Machine", pe_header.coff_header.machine - ))); - items.push(ListItem::new(format!( - "{}: {}", - "Compilation Timestamp", pe_header.coff_header.time_date_stamp - ))); - - if let Some(opt) = &pe_header.optional_header { - items.push(ListItem::new(format!( - "{}: {:X}", - "Entrypoint", opt.address_of_entry_point - ))); - items.push(ListItem::new(format!("{}: {:X}", "Magic", opt.magic))); - items.push(ListItem::new(format!( - "{}: {}", - "Minor linker version", opt.minor_linker_version - ))); - items.push(ListItem::new(format!( - "{}: {}", - "Major linker version", opt.major_linker_version - ))); +fn draw_dos_header(app: &mut App, frame: &mut Frame, area: Rect) { + if let Some(pe) = &app.header_view.pe { + let mut rows = Vec::new(); + + rows.push(Row::new(vec![ + Cell::new("Signature"), + Cell::new(format!("{:X}", pe.dos_header.signature)), + ])); + rows.push(Row::new(vec![ + Cell::new("BytesOnLastPage"), + Cell::new(format!("{:X}", pe.dos_header.bytes_on_last_page)), + ])); + rows.push(Row::new(vec![ + Cell::new("PagesInFile"), + Cell::new(format!("{:X}", pe.dos_header.pages_in_file)), + ])); + rows.push(Row::new(vec![ + Cell::new("Relocations"), + Cell::new(format!("{:X}", pe.dos_header.relocations)), + ])); + rows.push(Row::new(vec![ + Cell::new("SizeOfHeaderInParagraphs"), + Cell::new(format!("{:X}", pe.dos_header.size_of_header_in_paragraphs)), + ])); + rows.push(Row::new(vec![ + Cell::new("MinimumExtraParagraphsNeeded"), + Cell::new(format!( + "{:X}", + pe.dos_header.minimum_extra_paragraphs_needed + )), + ])); + rows.push(Row::new(vec![ + Cell::new("MaximumExtraParagraphsNeeded"), + Cell::new(format!( + "{:X}", + pe.dos_header.maximum_extra_paragraphs_needed + )), + ])); + rows.push(Row::new(vec![ + Cell::new("InitialRelativeSS"), + Cell::new(format!("{:X}", pe.dos_header.initial_relative_ss)), + ])); + rows.push(Row::new(vec![ + Cell::new("InitialSP"), + Cell::new(format!("{:X}", pe.dos_header.initial_sp)), + ])); + rows.push(Row::new(vec![ + Cell::new("Checksum"), + Cell::new(format!("{:X}", pe.dos_header.checksum)), + ])); + rows.push(Row::new(vec![ + Cell::new("InitialIP"), + Cell::new(format!("{:X}", pe.dos_header.initial_ip)), + ])); + rows.push(Row::new(vec![ + Cell::new("InitialRelativeCS"), + Cell::new(format!("{:X}", pe.dos_header.initial_relative_cs)), + ])); + rows.push(Row::new(vec![ + Cell::new("FileAddressOfRelocationTable"), + Cell::new(format!( + "{:X}", + pe.dos_header.file_address_of_relocation_table + )), + ])); + rows.push(Row::new(vec![ + Cell::new("OverlayNumber"), + Cell::new(format!("{:X}", pe.dos_header.overlay_number)), + ])); + rows.push(Row::new(vec![ + Cell::new("Reserved"), + Cell::new(format!("{:?}", pe.dos_header.reserved)), + ])); + rows.push(Row::new(vec![ + Cell::new("OemId"), + Cell::new(format!("{:X}", pe.dos_header.oem_id)), + ])); + rows.push(Row::new(vec![ + Cell::new("OemInfo"), + Cell::new(format!("{:X}", pe.dos_header.oem_info)), + ])); + rows.push(Row::new(vec![ + Cell::new("Reserved2"), + Cell::new(format!("{:?}", pe.dos_header.reserved2)), + ])); + rows.push(Row::new(vec![ + Cell::new("PEHeaderOffset"), + Cell::new(format!("{:X}", pe.dos_header.pe_pointer)), + ])); + + let widths = [Constraint::Min(16), Constraint::Fill(1)]; + + let header_table = Table::new(rows, widths) + .column_spacing(1) + .style(app.config.theme.main) + .cell_highlight_style(app.config.theme.highlight); + + frame.render_stateful_widget( + header_table, + area, + &mut app.header_view.pe_state.dos_header_table_state, + ); } +} - let headers_block = Block::new() - .borders(Borders::all()) - .title(pe_header.summary.clone()) - .title_alignment(Alignment::Center); +fn draw_pe_header(app: &mut App, frame: &mut Frame, area: Rect) { + if let Some(pe) = &app.header_view.pe { + let mut rows = Vec::new(); - let header_listing = List::new(items) - .style(app.config.theme.main) - .highlight_style(app.config.theme.highlight) - .block(headers_block); + let machine = pe.coff_header.machine; + rows.push(Row::new(vec![ + Cell::new("Machine"), + Cell::new(format!( + "{:X} ({})", + machine, + get_pe_machine_string(machine) + )), + ])); + rows.push(Row::new(vec![ + Cell::new("Characteristics"), + Cell::new(format!("{:X}", pe.coff_header.characteristics)), + ])); + rows.push(Row::new(vec![ + Cell::new("Compilation Timestamp"), + Cell::new(format!("{:X}", pe.coff_header.time_date_stamp)), + ])); + rows.push(Row::new(vec![ + Cell::new("Number of sections"), + Cell::new( + pe.optional_header + .unwrap() + .windows_fields + .number_of_rva_and_sizes + .to_string(), + ), + ])); - // section table + if let Some(opt) = &pe.optional_header { + rows.push(Row::new(vec![ + Cell::new("Entrypoint"), + Cell::new(format!("{:X}", opt.standard_fields.address_of_entry_point)), + ])); + rows.push(Row::new(vec![ + Cell::new("Magic"), + Cell::new(format!("{:X}", opt.standard_fields.magic)), + ])); + rows.push(Row::new(vec![ + Cell::new("Minor linker version"), + Cell::new(opt.standard_fields.minor_linker_version.to_string()), + ])); + rows.push(Row::new(vec![ + Cell::new("Major linker version"), + Cell::new(opt.standard_fields.major_linker_version.to_string()), + ])); + } - let mut rows = Vec::new(); + let widths = [Constraint::Min(16), Constraint::Fill(1)]; - for sec in &pe_header.sections { - let mut cells = Vec::new(); - cells.push(Cell::new(sec.name.as_str())); - cells.push(Cell::new(format!("{:08X}", sec.virtual_address))); - cells.push(Cell::new(format!("{:08X}", sec.virtual_size))); - cells.push(Cell::new(format!("{:08X}", sec.pointer_to_raw_data))); - cells.push(Cell::new(format!("{:08X}", sec.size_of_raw_data))); - cells.push(Cell::new(format!("{:08X}", sec.characteristics))); + let header_table = Table::new(rows, widths) + .column_spacing(1) + .style(app.config.theme.main) + .cell_highlight_style(app.config.theme.highlight); - rows.push(Row::new(cells)); + frame.render_widget(header_table, area); } +} + +fn draw_sections(app: &mut App, frame: &mut Frame, area: Rect) { + if let Some(pe) = &app.header_view.pe { + let mut rows = Vec::new(); + + for sec in &pe.sections { + let mut cells = Vec::new(); + cells.push(Cell::new(sec.name().unwrap_or(""))); + cells.push(Cell::new(format!("{:08X}", sec.virtual_address))); + cells.push(Cell::new(format!("{:08X}", sec.virtual_size))); + cells.push(Cell::new(format!("{:08X}", sec.pointer_to_raw_data))); + cells.push(Cell::new(format!("{:08X}", sec.size_of_raw_data))); + cells.push(Cell::new(format!("{:08X}", sec.characteristics))); + rows.push(Row::new(cells)); + } - let sections_block = Block::new() - .borders(Borders::all()) - .title("Sections") - .title_alignment(Alignment::Center); - - let widths = [Constraint::Ratio(1, 6); 6]; - - let section_table = Table::new(rows, widths) - .column_spacing(1) - .style(app.config.theme.main) - .header( - Row::new(vec![ - "Name", - "VirtualAddress", - "VirtualSize", - "PtrToRawData", - "SizeOfRawData", - "Characteristics", - ]) - .style(Style::new().bold()), - ) - .cell_highlight_style(app.config.theme.highlight) - .block(sections_block); - - // imports - let mut rows = Vec::new(); - - for imp in &pe_header.imports { - let mut cells = Vec::new(); - cells.push(Cell::new(imp.dll.as_str())); - cells.push(Cell::new(imp.name.as_str())); - cells.push(Cell::new(format!("{:08X}", imp.offset))); - cells.push(Cell::new(format!("{:08X}", imp.ordinal))); - cells.push(Cell::new(format!("{:08X}", imp.rva))); - // cells.push(Cell::new(format!("{:08X}", imp.size))); - - rows.push(Row::new(cells)); + let widths = [Constraint::Ratio(1, 6); 6]; + + let section_table = Table::new(rows, widths) + .column_spacing(1) + .style(app.config.theme.main) + .header( + Row::new(vec![ + "Name", + "VirtualAddress", + "VirtualSize", + "PtrToRawData", + "SizeOfRawData", + "Characteristics", + ]) + .style(Style::new().bold()), + ) + .cell_highlight_style(app.config.theme.highlight); + + frame.render_stateful_widget( + section_table, + area, + &mut app.header_view.pe_state.sections_table_state, + ); } +} + +fn draw_imports(app: &mut App, frame: &mut Frame, area: Rect) { + if let Some(pe) = &app.header_view.pe { + let mut rows = Vec::new(); + + for imp in &pe.imports { + let mut cells = Vec::new(); + cells.push(Cell::new(imp.dll.as_str())); + cells.push(Cell::new(imp.name.as_str())); + cells.push(Cell::new(format!("{:08X}", imp.offset))); + cells.push(Cell::new(format!("{:08X}", imp.ordinal))); + cells.push(Cell::new(format!("{:08X}", imp.rva))); + rows.push(Row::new(cells)); + } - let imports_block = Block::new() - .borders(Borders::all()) - .title("Imports") - .title_alignment(Alignment::Center); - - let widths = [Constraint::Ratio(1, 5); 5]; - - let imports_table = Table::new(rows, widths) - .column_spacing(1) - .style(app.config.theme.main) - .header( - Row::new(vec![ - "DLL", "Name", "Offset", "Ordinal", "RVA", - // "Size", - ]) - .style(Style::new().bold()), - ) - .cell_highlight_style(app.config.theme.highlight) - .block(imports_block); - - let layout = Layout::vertical([ - Constraint::Length(9), - Constraint::Percentage(50), - Constraint::Percentage(50), - ]); - let sub_area = area.layout_vec(&layout); + let widths = [Constraint::Ratio(1, 5); 5]; + + let imports_table = Table::new(rows, widths) + .column_spacing(1) + .style(app.config.theme.main) + .header( + Row::new(vec!["DLL", "Name", "Offset", "Ordinal", "RVA"]) + .style(Style::new().bold()), + ) + .cell_highlight_style(app.config.theme.highlight); + + frame.render_stateful_widget( + imports_table, + area, + &mut app.header_view.pe_state.imports_table_sate, + ); + } +} + +fn draw_overlay(app: &mut App, frame: &mut Frame, area: Rect) { + if let Some(pe) = &app.header_view.pe { + let overlay_start = pe + .sections + .iter() + .map(|sec| sec.pointer_to_raw_data.saturating_add(sec.size_of_raw_data) as usize) + .max() + .unwrap_or(pe.dos_header.pe_pointer as usize); + + let overlay_size = app.file_info.size.saturating_sub(overlay_start); + + let mut rows = Vec::new(); + + rows.push(Row::new(vec![ + Cell::new("OverlayStart"), + Cell::new(format!("{:08X}", overlay_start)), + ])); + rows.push(Row::new(vec![ + Cell::new("OverlaySize"), + Cell::new(format!("{:08X}", overlay_size)), + ])); + + let widths = [Constraint::Min(16), Constraint::Fill(1)]; + + let overlay_table = Table::new(rows, widths) + .column_spacing(1) + .style(app.config.theme.main) + .cell_highlight_style(app.config.theme.highlight); + + frame.render_widget(overlay_table, area); + } +} + +pub fn pe_draw(app: &mut App, frame: &mut Frame, area: Rect) { + let tabs = Tabs::new(vec![ + "DOS Header", + "PE Header", + "Sections", + "Imports", + "Overlay", + ]) + .style(app.config.theme.main) + .highlight_style(app.config.theme.highlight) + .divider("|") + .padding(" ", " ") + .select(app.header_view.tab_index); + + let layout = Layout::vertical([Constraint::Length(1), Constraint::Fill(1)]); + let [top, main] = area.layout(&layout); frame.render_widget(Clear, area); - // frame.render_widget(tabs, area); - frame.render_stateful_widget( - header_listing, - sub_area[0], - &mut app.header_view.header_list_state, - ); - frame.render_stateful_widget( - section_table, - sub_area[1], - &mut app.header_view.section_table_state, - ); - frame.render_stateful_widget( - imports_table, - sub_area[2], - &mut app.header_view.imports_table_state, - ); + frame.render_widget(tabs, top); + + match app.header_view.tab_index { + 0 => draw_dos_header(app, frame, main), + 1 => draw_pe_header(app, frame, main), + 2 => draw_sections(app, frame, main), + 3 => draw_imports(app, frame, main), + 4 => draw_overlay(app, frame, main), + _ => {} + } +} + +fn get_pe_machine_string(mach: u16) -> &'static str { + match mach { + goblin::pe::header::COFF_MACHINE_ARM => "ARM", + goblin::pe::header::COFF_MACHINE_ARM64 => "AARCH64", + goblin::pe::header::COFF_MACHINE_X86 => "x86", + goblin::pe::header::COFF_MACHINE_X86_64 => "x86-64", + _ => "", + } } diff --git a/src/header/formats/pe/events.rs b/src/header/formats/pe/events.rs index cac9958..d53b347 100644 --- a/src/header/formats/pe/events.rs +++ b/src/header/formats/pe/events.rs @@ -2,87 +2,197 @@ use std::io::Result; use ratatui::crossterm::event::{KeyCode, KeyEvent}; -use crate::{app::App, editor::AppView}; +use crate::app::App; -pub fn header_pe_view_events(app: &mut App, key: KeyEvent) -> Result { +const NUMBER_OF_TABS: usize = 5; + +fn tab_next(app: &mut App) { + if app.header_view.tab_index < NUMBER_OF_TABS - 1 { + app.header_view.tab_index = app.header_view.tab_index.saturating_add(1); + } else { + app.header_view.tab_index = 0; + } +} + +fn tab_prev(app: &mut App) { + if app.header_view.tab_index == 0 { + app.header_view.tab_index = NUMBER_OF_TABS - 1; + } else { + app.header_view.tab_index -= 1; + } +} + +fn tab_dos_header_events(app: &mut App, key: KeyEvent) -> Result { match key.code { + KeyCode::Down | KeyCode::Char('j') => { + if app + .header_view + .pe_state + .dos_header_table_state + .selected_cell() + .is_none() + { + app.header_view + .pe_state + .dos_header_table_state + .select_cell(Some((0, 1))); + } else { + app.header_view + .pe_state + .dos_header_table_state + .select_next(); + } + } KeyCode::Up | KeyCode::Char('k') => { - if let Some(idx) = app.header_view.section_table_state.selected() { - if idx == 0 { - app.header_view.section_table_state.select(None); - app.header_view.section_table_state.select_column(None); - app.header_view.header_list_state.select_last(); - } else { - app.header_view.section_table_state.select_previous(); - } - } else if app.header_view.header_list_state.selected().is_some() { - app.header_view.header_list_state.select_previous(); - } else if let Some(idx) = app.header_view.imports_table_state.selected() { + if let Some(idx) = app.header_view.pe_state.dos_header_table_state.selected() { if idx == 0 { - app.header_view.imports_table_state.select(None); - app.header_view.imports_table_state.select_column(None); - app.header_view.section_table_state.select_last(); + app.header_view.pe_state.dos_header_table_state.select(None); } else { - app.header_view.imports_table_state.select_previous(); + app.header_view + .pe_state + .dos_header_table_state + .select_previous(); } } } + KeyCode::Char('G') => { + // goto + } + _ => {} + } + Ok(false) +} + +fn tab_pe_header_events(app: &mut App, key: KeyEvent) -> Result { + match key.code { KeyCode::Down | KeyCode::Char('j') => { - if let Some(idx) = app.header_view.header_list_state.selected() { - if idx == 6 { - app.header_view.header_list_state.select(None); - app.header_view - .section_table_state - .select_cell(Some((0, 0))); + if app + .header_view + .pe_state + .pe_header_table_state + .selected_cell() + .is_none() + { + app.header_view + .pe_state + .pe_header_table_state + .select_cell(Some((0, 1))); + } else { + app.header_view.pe_state.pe_header_table_state.select_next(); + } + } + KeyCode::Up | KeyCode::Char('k') => { + if let Some(idx) = app.header_view.pe_state.pe_header_table_state.selected() { + if idx == 0 { + app.header_view.pe_state.pe_header_table_state.select(None); } else { - app.header_view.header_list_state.select_next(); - } - } else if let Some(idx) = app.header_view.section_table_state.selected() { - if idx + 1 == app.header_view.pe.as_ref().unwrap().number_of_sections { - app.header_view.section_table_state.select(None); app.header_view - .imports_table_state - .select_cell(Some((0, 1))); - } else { - app.header_view.section_table_state.select_next(); + .pe_state + .pe_header_table_state + .select_previous(); } - } else if app.header_view.imports_table_state.selected().is_some() { - app.header_view.imports_table_state.select_next(); } } - KeyCode::Left | KeyCode::Char('h') => { - if app.header_view.section_table_state.selected().is_some() { - app.header_view.section_table_state.select_previous_column(); - } + KeyCode::Char('G') => { + // goto } - KeyCode::Right | KeyCode::Char('l') => { - if app.header_view.section_table_state.selected().is_some() { - app.header_view.section_table_state.select_next_column(); + _ => {} + } + Ok(false) +} + +fn tab_sections_events(app: &mut App, key: KeyEvent) -> Result { + match key.code { + KeyCode::Down | KeyCode::Char('j') => { + if app + .header_view + .pe_state + .sections_table_state + .selected_cell() + .is_none() + { + app.header_view + .pe_state + .sections_table_state + .select_cell(Some((0, 1))); + } else { + app.header_view.pe_state.sections_table_state.select_next(); } } - KeyCode::Char('G') => { - if let Some(idx) = app.header_view.header_list_state.selected() { - if idx == 3 { - app.goto(app.header_view.entrypoint as usize); - app.editor_view = AppView::Hex; + KeyCode::Up | KeyCode::Char('k') => { + if let Some(idx) = app.header_view.pe_state.sections_table_state.selected() { + if idx == 0 { + app.header_view.pe_state.sections_table_state.select(None); + } else { + app.header_view + .pe_state + .sections_table_state + .select_previous(); } } } - KeyCode::Tab => { - if app.header_view.tab_index < 3 { - app.header_view.tab_index += 1; + KeyCode::Char('G') => { + // goto + } + _ => {} + } + Ok(false) +} + +fn tab_imports_events(app: &mut App, key: KeyEvent) -> Result { + match key.code { + KeyCode::Down | KeyCode::Char('j') => { + if app + .header_view + .pe_state + .imports_table_sate + .selected_cell() + .is_none() + { + app.header_view + .pe_state + .imports_table_sate + .select_cell(Some((0, 1))); } else { - app.header_view.tab_index = 0; + app.header_view.pe_state.imports_table_sate.select_next(); } } - KeyCode::BackTab => { - if app.header_view.tab_index > 0 { - app.header_view.tab_index -= 1; - } else { - app.header_view.tab_index = 3; + KeyCode::Up | KeyCode::Char('k') => { + if let Some(idx) = app.header_view.pe_state.imports_table_sate.selected() { + if idx == 0 { + app.header_view.pe_state.imports_table_sate.select(None); + } else { + app.header_view + .pe_state + .imports_table_sate + .select_previous(); + } } } + KeyCode::Char('G') => { + // goto + } _ => {} } Ok(false) } + +pub fn view_header_pe_events(app: &mut App, key: KeyEvent) -> Result { + match key.code { + KeyCode::Tab | KeyCode::Right | KeyCode::Char('l') => { + tab_next(app); + } + KeyCode::BackTab | KeyCode::Left | KeyCode::Char('h') => { + tab_prev(app); + } + _ => (), + } + + match app.header_view.tab_index { + 0 => tab_dos_header_events(app, key), + 1 => tab_pe_header_events(app, key), + 2 => tab_sections_events(app, key), + 3 => tab_imports_events(app, key), + _ => Ok(false), + } +} diff --git a/src/header/header_view.rs b/src/header/header_view.rs index 05e45d6..fc570af 100644 --- a/src/header/header_view.rs +++ b/src/header/header_view.rs @@ -1,29 +1,4 @@ -use ratatui::widgets::{ListState, TableState}; - -#[derive(Debug, Clone)] -pub struct PECoffHeader { - pub characteristics: u16, - pub machine: u16, - pub time_date_stamp: u32, -} - -#[derive(Debug, Clone)] -pub struct PEStandardFields { - pub address_of_entry_point: u32, - pub magic: u16, - pub minor_linker_version: u8, - pub major_linker_version: u8, -} - -#[derive(Debug, Clone)] -pub struct PESection { - pub name: String, - pub virtual_address: u32, - pub virtual_size: u32, - pub pointer_to_raw_data: u32, - pub size_of_raw_data: u32, - pub characteristics: u32, -} +use ratatui::widgets::TableState; #[derive(Debug, Clone)] pub struct PEImport { @@ -37,11 +12,10 @@ pub struct PEImport { #[derive(Debug, Clone)] pub struct Pe { - pub summary: String, - pub coff_header: PECoffHeader, - pub optional_header: Option, - pub sections: Vec, - pub number_of_sections: usize, + pub dos_header: goblin::pe::header::DosHeader, + pub coff_header: goblin::pe::header::CoffHeader, + pub optional_header: Option, + pub sections: Vec, pub imports: Vec, } @@ -53,16 +27,27 @@ pub struct Elf { pub symtab: Vec, } +#[derive(Debug, Default)] +pub struct PeState { + pub dos_header_table_state: TableState, + pub pe_header_table_state: TableState, + pub sections_table_state: TableState, + pub imports_table_sate: TableState, +} + +#[derive(Debug, Default)] +pub struct ElfState { + pub elf_header_table_state: TableState, + pub program_header_table_state: TableState, + pub sections_table_state: TableState, + pub symbols_table_state: TableState, +} + #[derive(Default, Debug)] pub struct HeaderView { - pub header_list_state: ListState, - pub section_table_state: TableState, - pub imports_table_state: TableState, - pub entrypoint: u32, pub pe: Option, pub elf: Option, - pub elf_header_table_state: TableState, - pub elf_phrs_table_state: TableState, - pub elf_sections_table_state: TableState, + pub elf_state: ElfState, + pub pe_state: PeState, pub tab_index: usize, } From 48055d4e393728baf7bd201ab4aec78e8e49e4bb Mon Sep 17 00:00:00 2001 From: merces Date: Sat, 20 Jun 2026 21:21:03 -0300 Subject: [PATCH 12/13] Show ELF symbol names instead of strtab indices --- src/app.rs | 6 ++++++ src/header/formats/elf/draw.rs | 9 ++++++++- src/header/header_view.rs | 3 +++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/app.rs b/src/app.rs index 62cee6e..00677bd 100644 --- a/src/app.rs +++ b/src/app.rs @@ -153,6 +153,12 @@ impl App { phdrs: elf.program_headers.clone(), sections: elf.section_headers.clone(), 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(), }); "ELF" } diff --git a/src/header/formats/elf/draw.rs b/src/header/formats/elf/draw.rs index 453845e..b93d6e9 100644 --- a/src/header/formats/elf/draw.rs +++ b/src/header/formats/elf/draw.rs @@ -265,8 +265,15 @@ fn draw_symbols(app: &mut App, frame: &mut Frame, area: Rect) { let mut rows = Vec::new(); for symbol in &elf.symtab { + let name = elf + .strtab + .get(&symbol.st_name) + .map(String::as_str) + .unwrap_or_default(); + rows.push(Row::new(vec![ - Cell::new(format!("{:X}", symbol.st_name)), + // Cell::new(format!("{:X}", symbol.st_name)), + Cell::from(name), Cell::new(format!("{}", bind_to_str(symbol.st_bind()))), Cell::new(format!("{}", type_to_str(symbol.st_type()))), Cell::new(format!("{}", visibility_to_str(symbol.st_visibility()))), diff --git a/src/header/header_view.rs b/src/header/header_view.rs index fc570af..e4aeb2e 100644 --- a/src/header/header_view.rs +++ b/src/header/header_view.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use ratatui::widgets::TableState; #[derive(Debug, Clone)] @@ -25,6 +27,7 @@ pub struct Elf { pub phdrs: goblin::elf::ProgramHeaders, pub sections: goblin::elf::SectionHeaders, pub symtab: Vec, + pub strtab: HashMap, } #[derive(Debug, Default)] From 9e9396f6909431c687bce29b36db59180600c74d Mon Sep 17 00:00:00 2001 From: merces Date: Sat, 20 Jun 2026 21:45:14 -0300 Subject: [PATCH 13/13] More space of ELF symbol names --- src/header/formats/elf/draw.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/header/formats/elf/draw.rs b/src/header/formats/elf/draw.rs index b93d6e9..479af53 100644 --- a/src/header/formats/elf/draw.rs +++ b/src/header/formats/elf/draw.rs @@ -272,8 +272,7 @@ fn draw_symbols(app: &mut App, frame: &mut Frame, area: Rect) { .unwrap_or_default(); rows.push(Row::new(vec![ - // Cell::new(format!("{:X}", symbol.st_name)), - Cell::from(name), + Cell::new(name), Cell::new(format!("{}", bind_to_str(symbol.st_bind()))), Cell::new(format!("{}", type_to_str(symbol.st_type()))), Cell::new(format!("{}", visibility_to_str(symbol.st_visibility()))), @@ -283,8 +282,15 @@ fn draw_symbols(app: &mut App, frame: &mut Frame, area: Rect) { ])); } - let widths = [Constraint::Ratio(1, 8); 8]; - + let widths = [ + Constraint::Fill(1), // Name + Constraint::Length(6), // Bind + Constraint::Length(6), // Type + Constraint::Length(10), // Visibility + Constraint::Length(8), // SecHdrIdx + Constraint::Length(8), // Value + Constraint::Length(8), // Size + ]; let symbol_table = Table::new(rows, widths) .column_spacing(1) .style(app.config.theme.main)