hex_patch/app/
hex.rs

1use std::error::Error;
2
3use ratatui::text::{Line, Span, Text};
4
5use crate::get_app_context;
6
7use super::{
8    asm::assembly_line::AssemblyLine, info_mode::InfoMode, pane::Pane,
9    settings::color_settings::ColorSettings, App,
10};
11
12pub(super) struct InstructionInfo {
13    pub offset: isize,
14    pub length: usize,
15    pub is_section: bool,
16}
17
18impl App {
19    pub(super) fn bytes_to_styled_hex(
20        color_settings: &ColorSettings,
21        bytes: &[u8],
22        block_size: usize,
23        blocks_per_row: usize,
24        selected_byte_index: usize,
25        high_byte: bool,
26        instruction_info: Option<InstructionInfo>,
27    ) -> Text<'static> {
28        let mut ret = Text::default();
29        ret.lines
30            .reserve(bytes.len() / (block_size * blocks_per_row) + 1);
31        let mut current_line = Line::default();
32        let mut local_block = 0;
33        let mut local_byte = 0;
34        for (byte_index, b) in bytes.iter().enumerate() {
35            let byte_index = byte_index as isize;
36            let mut next_line = false;
37            let hex_chars = Self::u8_to_hex(*b);
38            let hex_high = hex_chars[0].to_string();
39            let hex_low = hex_chars[1].to_string();
40            let (mut space_style, mut style) = (
41                color_settings.hex_default,
42                Self::get_style_for_byte(color_settings, *b),
43            );
44
45            if let Some(instruction_info) = &instruction_info {
46                let used_style = if instruction_info.is_section {
47                    color_settings.hex_current_section
48                } else {
49                    color_settings.hex_current_instruction
50                };
51                if byte_index >= instruction_info.offset
52                    && byte_index < instruction_info.offset + instruction_info.length as isize
53                {
54                    let is_last_space = byte_index
55                        == instruction_info.offset + instruction_info.length as isize - 1;
56                    if !is_last_space {
57                        space_style = used_style;
58                    }
59                    style = used_style;
60                }
61            }
62
63            let span = Span::styled(
64                hex_high,
65                if byte_index == selected_byte_index as isize && high_byte {
66                    color_settings.hex_selected
67                } else {
68                    style
69                },
70            );
71            current_line.spans.push(span);
72            let span = Span::styled(
73                hex_low,
74                if byte_index == selected_byte_index as isize && !high_byte {
75                    color_settings.hex_selected
76                } else {
77                    style
78                },
79            );
80            current_line.spans.push(span);
81            let mut spacing_string = " ".to_string();
82            local_byte += 1;
83            if local_byte % block_size == 0 {
84                local_byte = 0;
85                spacing_string.push(' ');
86
87                local_block += 1;
88                if local_block % blocks_per_row == 0 {
89                    local_block = 0;
90                    next_line = true;
91                }
92            }
93
94            let span = Span::styled(spacing_string, space_style);
95            current_line.spans.push(span);
96
97            if next_line {
98                let new_line = std::mem::take(&mut current_line);
99                ret.lines.push(new_line);
100            }
101        }
102        if !current_line.spans.is_empty() {
103            ret.lines.push(current_line);
104        }
105
106        ret
107    }
108
109    pub(super) fn resize_to_size(&mut self, width: u16, height: u16) {
110        let blocks_per_row: usize =
111            Self::calc_blocks_per_row(self.block_size, width, self.fullscreen, self.selected_pane);
112        if (width, height) != self.screen_size || blocks_per_row != self.blocks_per_row {
113            self.screen_size = (width, height);
114            self.resize(blocks_per_row);
115        }
116    }
117
118    pub(super) fn resize(&mut self, blocks_per_row: usize) {
119        let old_cursor = self.get_cursor_position();
120        self.blocks_per_row = blocks_per_row;
121
122        self.jump_to(old_cursor.global_byte_index, false);
123    }
124
125    pub(super) fn calc_blocks_per_row(
126        block_size: usize,
127        width: u16,
128        fullscreen: bool,
129        selected_pane: Pane,
130    ) -> usize {
131        let block_characters_hex = block_size * 3 + 1;
132        let block_characters_text = block_size * 2 + 1;
133        let available_width = width.saturating_sub(18 + 2 + 2);
134        let complessive_chars_per_block = if fullscreen {
135            match selected_pane {
136                Pane::Hex => block_characters_hex,
137                Pane::View => block_characters_text,
138            }
139        } else {
140            block_characters_hex + block_characters_text
141        };
142        let blocks_per_row = (available_width + 2) / complessive_chars_per_block as u16;
143        (blocks_per_row as usize).max(1)
144    }
145
146    pub(super) fn u8_to_hex(input: u8) -> [char; 2] {
147        let symbols = [
148            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
149        ];
150        let low = input & 0x0f;
151        let high = (input & 0xf0) >> 4;
152        [symbols[high as usize], symbols[low as usize]]
153    }
154
155    pub(super) fn edit_data(&mut self, mut value: char) -> Result<(), Box<dyn Error>> {
156        value = value.to_uppercase().next().unwrap();
157
158        if value.is_ascii_hexdigit() {
159            let cursor_position = self.get_cursor_position();
160
161            let old_byte = self.data.bytes()[cursor_position.global_byte_index];
162            let old_byte_str = format!("{:02X}", old_byte);
163            let new_byte_str = if cursor_position.high_byte {
164                format!("{}{}", value, old_byte_str.chars().nth(1).unwrap())
165            } else {
166                format!("{}{}", old_byte_str.chars().nth(0).unwrap(), value)
167            };
168            let new_byte = u8::from_str_radix(&new_byte_str, 16).unwrap();
169
170            let mut new_bytes = vec![new_byte];
171            let mut app_context = get_app_context!(self);
172
173            self.plugin_manager
174                .on_edit(&mut new_bytes, &mut app_context);
175
176            let modified_bytes = self
177                .data
178                .push_change(cursor_position.global_byte_index, new_bytes);
179
180            self.edit_assembly(modified_bytes);
181        }
182        Ok(())
183    }
184
185    /// start_row is included, end_row is excluded
186    pub(super) fn get_hex_view(&self, start_row: usize, end_row: usize) -> Text<'static> {
187        let start_byte = start_row * self.blocks_per_row * self.block_size;
188        let end_byte = end_row * self.blocks_per_row * self.block_size;
189        let end_byte = std::cmp::min(end_byte, self.data.len());
190        let bytes = &self.data.bytes()[start_byte..end_byte];
191        let selected_byte_index = self
192            .get_cursor_position()
193            .global_byte_index
194            .saturating_sub(start_byte);
195        let high_byte = self.get_cursor_position().high_byte;
196        let instruction_info = {
197            if self.info_mode == InfoMode::Assembly {
198                let current_instruction = self.get_current_instruction();
199                if let Some(assembly_line) = current_instruction {
200                    let offset = assembly_line.file_address() as isize - start_byte as isize;
201                    let length = assembly_line.len();
202                    let is_section = matches!(assembly_line, AssemblyLine::SectionTag(_));
203                    Some(InstructionInfo {
204                        offset,
205                        length,
206                        is_section,
207                    })
208                } else {
209                    None
210                }
211            } else {
212                None
213            }
214        };
215        Self::bytes_to_styled_hex(
216            &self.settings.color,
217            bytes,
218            self.block_size,
219            self.blocks_per_row,
220            selected_byte_index,
221            high_byte,
222            instruction_info,
223        )
224    }
225}
226
227#[cfg(test)]
228mod test {
229    use super::*;
230
231    #[test]
232    fn test_resize() {
233        let data = vec![0; 0x100];
234        let mut app = App::mockup(data);
235        app.resize_to_size(80, 24);
236        app.resize_to_size(80, 50);
237        app.resize_to_size(40, 24);
238        app.resize_to_size(250, 250);
239
240        app.resize_to_size(80, 1);
241        app.resize_to_size(250, 1);
242        app.resize_to_size(40, 1);
243        app.resize_to_size(1, 1);
244
245        app.resize_to_size(1, 50);
246        app.resize_to_size(1, 250);
247        app.resize_to_size(1, 24);
248        app.resize_to_size(1, 1);
249        app.resize_to_size(80, 24);
250    }
251
252    #[test]
253    fn test_u8_to_hex() {
254        assert_eq!(App::u8_to_hex(0x00), ['0', '0']);
255        assert_eq!(App::u8_to_hex(0x01), ['0', '1']);
256        assert_eq!(App::u8_to_hex(0x0A), ['0', 'A']);
257        assert_eq!(App::u8_to_hex(0x0F), ['0', 'F']);
258        assert_eq!(App::u8_to_hex(0x10), ['1', '0']);
259        assert_eq!(App::u8_to_hex(0x1F), ['1', 'F']);
260        assert_eq!(App::u8_to_hex(0xF0), ['F', '0']);
261        assert_eq!(App::u8_to_hex(0xFF), ['F', 'F']);
262    }
263}