summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdandi <rwindowed@gmail.com>2025-03-21 15:57:22 +0200
committerGitHub <noreply@github.com>2025-03-21 15:57:22 +0200
commit018d4287dc2a564b2daf8d595692024b91681ea3 (patch)
tree8a84529a6f08138d2842718b21430475dca4b46a
parent1fcd3aa2098de576f5551af0c332665f9233eb4c (diff)
Add files via upload
-rw-r--r--src/hex.rs35
-rw-r--r--src/input.rs23
-rw-r--r--src/main.rs17
-rw-r--r--src/render.rs67
4 files changed, 142 insertions, 0 deletions
diff --git a/src/hex.rs b/src/hex.rs
new file mode 100644
index 0000000..6dfbe7f
--- /dev/null
+++ b/src/hex.rs
@@ -0,0 +1,35 @@
+use std::{fs, io};
+use std::io::stdout;
+use ratatui::{backend::CrosstermBackend, Terminal};
+use crossterm::{execute, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}};
+use crate::{input, render};
+
+pub struct HexEditor {
+ filename: String,
+ data: Vec<u8>,
+ cursor: usize,
+ terminal: Terminal<CrosstermBackend<std::io::Stdout>>,
+}
+
+impl HexEditor {
+ pub fn new(filename: String, data: Vec<u8>) -> io::Result<Self> {
+ enable_raw_mode()?;
+ execute!(stdout(), EnterAlternateScreen)?;
+ let terminal = Terminal::new(CrosstermBackend::new(stdout()))?;
+ Ok(Self { filename, data, cursor: 0, terminal })
+ }
+
+ pub fn run(&mut self) -> io::Result<()> {
+ while input::handle_input(&mut self.cursor, &mut self.data[..])? {
+ render::draw(&mut self.terminal, &self.data, self.cursor, &self.filename)?;
+ }
+ self.cleanup()
+ }
+
+ pub fn cleanup(&mut self) -> io::Result<()> {
+ disable_raw_mode()?;
+ execute!(stdout(), LeaveAlternateScreen)?;
+ fs::write(&self.filename, &self.data)?;
+ Ok(())
+ }
+}
diff --git a/src/input.rs b/src/input.rs
new file mode 100644
index 0000000..0db7e16
--- /dev/null
+++ b/src/input.rs
@@ -0,0 +1,23 @@
+use std::{io, time::Duration};
+use crossterm::{event::{self, KeyCode, KeyEvent}};
+
+pub fn handle_input(cursor: &mut usize, data: &mut [u8]) -> io::Result<bool> {
+ if event::poll(Duration::from_millis(100))? {
+ if let event::Event::Key(KeyEvent { code, .. }) = event::read()? {
+ match code {
+ KeyCode::Esc => return Ok(false),
+ KeyCode::Left if *cursor > 0 => *cursor -= 1,
+ KeyCode::Right if *cursor < data.len() * 2 - 1 => *cursor += 1,
+ KeyCode::Up if *cursor >= 32 => *cursor -= 32,
+ KeyCode::Down if *cursor + 32 < data.len() * 2 => *cursor += 32,
+ KeyCode::Char(c) if c.is_ascii_hexdigit() => {
+ let byte_index = *cursor / 2;
+ let shift = if *cursor % 2 == 0 { 4 } else { 0 };
+ data[byte_index] = (data[byte_index] & (0x0F << (4 - shift))) | (c.to_digit(16).unwrap() as u8) << shift;
+ }
+ _ => {}
+ }
+ }
+ }
+ Ok(true)
+}
diff --git a/src/main.rs b/src/main.rs
index 8b13789..a79f7cb 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1 +1,18 @@
+mod hex;
+mod input;
+mod render;
+use hex::HexEditor;
+use std::{env, fs, io};
+
+fn main() -> io::Result<()> {
+ let args: Vec<String> = env::args().collect();
+ if args.len() < 2 {
+ return Ok(());
+ }
+
+ let filename = args[1].clone();
+ let data = fs::read(&filename)?;
+ let mut editor = HexEditor::new(filename, data)?;
+ editor.run()
+}
diff --git a/src/render.rs b/src/render.rs
new file mode 100644
index 0000000..a0cb234
--- /dev/null
+++ b/src/render.rs
@@ -0,0 +1,67 @@
+use std::io;
+use ratatui::{
+ layout::{Constraint, Direction, Layout},
+ style::{Color, Style},
+ text::{Line, Span},
+ widgets::{Block, Borders, Paragraph},
+ Terminal,
+ prelude::Alignment,
+};
+
+pub fn draw(terminal: &mut Terminal<impl ratatui::backend::Backend>, data: &[u8], cursor: usize, filename: &str) -> io::Result<()> {
+ terminal.draw(|frame| {
+ let size = frame.size();
+ let layout = Layout::default()
+ .direction(Direction::Vertical)
+ .constraints([Constraint::Min(1), Constraint::Length(1)])
+ .split(size);
+
+ let lines: Vec<Line> = data.chunks(16).enumerate().map(|(i, chunk)| {
+ let addr = Span::raw(format!("{:08x} │ ", i * 16));
+ let mut hex: Vec<Span> = Vec::new();
+ let mut ascii_repr = String::new();
+
+ for (j, &byte) in chunk.iter().enumerate() {
+ let hex_str = format!("{:02x}", byte);
+ let index = i * 32 + j * 2;
+
+ for (k, ch) in hex_str.chars().enumerate() {
+ let cur_idx = index + k;
+ let style = if cur_idx == cursor {
+ Style::default().bg(Color::White).fg(Color::Black)
+ } else {
+ Style::default()
+ };
+ hex.push(Span::styled(ch.to_string(), style));
+ }
+
+ if j < 15 {
+ let space = if j % 4 == 3 { " " } else { " " };
+ hex.push(Span::raw(space));
+ }
+
+ ascii_repr.push(if byte.is_ascii_graphic() || byte == b' ' { byte as char } else { '.' });
+ }
+
+ let missing_bytes = 16 - chunk.len();
+ let padding = missing_bytes * 3;
+ let spaces = " ".repeat(padding);
+
+ let ascii = Span::raw(format!("{} │ {}", spaces, ascii_repr));
+
+ Line::from(vec![addr]
+ .into_iter()
+ .chain(hex)
+ .chain(std::iter::once(ascii))
+ .collect::<Vec<_>>())
+ }).collect();
+
+ frame.render_widget(Paragraph::new(lines).block(Block::default().borders(Borders::ALL)), layout[0]);
+
+ let progress = if data.is_empty() { 100 } else { (cursor / 2 * 100) / data.len() };
+ let status_text = format!("{} \t 0x{:08x} \t {:3}%", filename, cursor / 2, progress);
+ let status_bar = Paragraph::new(Line::from(Span::raw(status_text))).alignment(Alignment::Center);
+ frame.render_widget(status_bar, layout[1]);
+ })?;
+ Ok(())
+}