nu_plugin_binaryview/
binaryview.rs

1use crossterm::{style::Attribute, ExecutableCommand};
2use nu_pretty_hex::*;
3use nu_protocol::outln;
4use nu_source::AnchorLocation;
5
6#[derive(Default)]
7pub struct BinaryView;
8
9impl BinaryView {
10    pub fn new() -> BinaryView {
11        BinaryView
12    }
13}
14
15pub fn view_binary(
16    b: &[u8],
17    source: Option<&AnchorLocation>,
18    lores_mode: bool,
19) -> Result<(), Box<dyn std::error::Error>> {
20    if b.len() > 3 {
21        if let (0x4e, 0x45, 0x53) = (b[0], b[1], b[2]) {
22            view_contents_interactive(b, source, lores_mode)?;
23            return Ok(());
24        }
25    }
26
27    view_contents(b, source, lores_mode)?;
28    Ok(())
29}
30
31pub struct RenderContext {
32    pub width: usize,
33    pub height: usize,
34    pub frame_buffer: Vec<(u8, u8, u8)>,
35    pub since_last_button: Vec<usize>,
36    pub lores_mode: bool,
37}
38
39impl RenderContext {
40    pub fn blank(lores_mode: bool) -> RenderContext {
41        RenderContext {
42            width: 0,
43            height: 0,
44            frame_buffer: vec![],
45            since_last_button: vec![0; 8],
46            lores_mode,
47        }
48    }
49    pub fn clear(&mut self) {
50        self.frame_buffer = vec![(0, 0, 0); self.width * self.height as usize];
51    }
52
53    fn render_to_screen_lores(&mut self) {
54        let mut prev_color: Option<(u8, u8, u8)> = None;
55        let mut prev_count = 1;
56
57        let _ = std::io::stdout().execute(crossterm::cursor::MoveTo(0, 0));
58
59        for pixel in &self.frame_buffer {
60            match prev_color {
61                Some(c) if c == *pixel => {
62                    prev_count += 1;
63                }
64                Some(c) => {
65                    print!(
66                        "{}",
67                        nu_ansi_term::Color::Rgb(c.0, c.1, c.2)
68                            .paint((0..prev_count).map(|_| "█").collect::<String>())
69                    );
70                    prev_color = Some(*pixel);
71                    prev_count = 1;
72                }
73                _ => {
74                    prev_color = Some(*pixel);
75                    prev_count = 1;
76                }
77            }
78        }
79
80        if prev_count > 0 {
81            if let Some(color) = prev_color {
82                print!(
83                    "{}",
84                    nu_ansi_term::Color::Rgb(color.0, color.1, color.2)
85                        .paint((0..prev_count).map(|_| "█").collect::<String>())
86                );
87            }
88        }
89        outln!("{}", Attribute::Reset);
90    }
91    fn render_to_screen_hires(&mut self) {
92        let mut prev_fg: Option<(u8, u8, u8)> = None;
93        let mut prev_bg: Option<(u8, u8, u8)> = None;
94        let mut prev_count = 1;
95
96        let mut pos = 0;
97        let fb_len = self.frame_buffer.len();
98
99        let _ = std::io::stdout().execute(crossterm::cursor::MoveTo(0, 0));
100
101        while pos < (fb_len - self.width) {
102            let top_pixel = self.frame_buffer[pos];
103            let bottom_pixel = self.frame_buffer[pos + self.width];
104
105            match (prev_fg, prev_bg) {
106                (Some(c), Some(d)) if c == top_pixel && d == bottom_pixel => {
107                    prev_count += 1;
108                }
109                (Some(c), Some(d)) => {
110                    print!(
111                        "{}",
112                        nu_ansi_term::Color::Rgb(c.0, c.1, c.2)
113                            .on(nu_ansi_term::Color::Rgb(d.0, d.1, d.2,))
114                            .paint((0..prev_count).map(|_| "▀").collect::<String>())
115                    );
116                    prev_fg = Some(top_pixel);
117                    prev_bg = Some(bottom_pixel);
118                    prev_count = 1;
119                }
120                _ => {
121                    prev_fg = Some(top_pixel);
122                    prev_bg = Some(bottom_pixel);
123                    prev_count = 1;
124                }
125            }
126            pos += 1;
127            if pos % self.width == 0 {
128                pos += self.width;
129            }
130        }
131        if prev_count > 0 {
132            if let (Some(c), Some(d)) = (prev_fg, prev_bg) {
133                print!(
134                    "{}",
135                    nu_ansi_term::Color::Rgb(c.0, c.1, c.2)
136                        .on(nu_ansi_term::Color::Rgb(d.0, d.1, d.2,))
137                        .paint((0..prev_count).map(|_| "▀").collect::<String>())
138                );
139            }
140        }
141        outln!("{}", Attribute::Reset);
142    }
143    pub fn flush(&mut self) {
144        if self.lores_mode {
145            self.render_to_screen_lores()
146        } else {
147            self.render_to_screen_hires()
148        }
149    }
150    pub fn update(&mut self) -> Result<(), Box<dyn std::error::Error>> {
151        let terminal_size = crossterm::terminal::size().unwrap_or((80, 24));
152
153        if (self.width != terminal_size.0 as usize) || (self.height != terminal_size.1 as usize) {
154            let _ = std::io::stdout().execute(crossterm::cursor::Hide);
155
156            self.width = terminal_size.0 as usize;
157            self.height = if self.lores_mode {
158                terminal_size.1 as usize - 1
159            } else {
160                (terminal_size.1 as usize - 1) * 2
161            };
162        }
163
164        Ok(())
165    }
166}
167
168#[derive(Debug)]
169struct RawImageBuffer {
170    dimensions: (u32, u32),
171    colortype: image::ColorType,
172    buffer: Vec<u8>,
173}
174
175fn load_from_png_buffer(buffer: &[u8]) -> Result<RawImageBuffer, Box<dyn std::error::Error>> {
176    use image::ImageDecoder;
177
178    let decoder = image::codecs::png::PngDecoder::new(buffer)?;
179
180    let dimensions = decoder.dimensions();
181    let colortype = decoder.color_type();
182    let mut buffer: Vec<u8> = vec![0; decoder.total_bytes() as usize];
183    decoder.read_image(&mut buffer)?;
184
185    Ok(RawImageBuffer {
186        dimensions,
187        colortype,
188        buffer,
189    })
190}
191
192fn load_from_jpg_buffer(buffer: &[u8]) -> Result<RawImageBuffer, Box<dyn std::error::Error>> {
193    use image::ImageDecoder;
194
195    let decoder = image::codecs::jpeg::JpegDecoder::new(buffer)?;
196
197    let dimensions = decoder.dimensions();
198    let colortype = decoder.color_type();
199    let mut buffer: Vec<u8> = vec![0; decoder.total_bytes() as usize];
200    decoder.read_image(&mut buffer)?;
201
202    Ok(RawImageBuffer {
203        dimensions,
204        colortype,
205        buffer,
206    })
207}
208
209pub fn view_contents(
210    buffer: &[u8],
211    _source: Option<&AnchorLocation>,
212    lores_mode: bool,
213) -> Result<(), Box<dyn std::error::Error>> {
214    // Some 'bad actor' binaries turn off ansi support so we need to make sure
215    // that ansi support is enabled in windows
216    #[cfg(windows)]
217    {
218        let _ = nu_ansi_term::enable_ansi_support();
219    }
220
221    // we never use skip and length here because the composable pipeline should do that part
222    let hex_config = HexConfig {
223        title: true,
224        ascii: true,
225        width: 16,
226        group: 4,
227        chunk: 1,
228        skip: None,
229        length: None,
230    };
231
232    let mut raw_image_buffer = load_from_png_buffer(buffer);
233
234    if raw_image_buffer.is_err() {
235        raw_image_buffer = load_from_jpg_buffer(buffer);
236    }
237
238    if raw_image_buffer.is_err() {
239        //Not yet supported
240        outln!("{}", config_hex(&buffer, hex_config));
241        return Ok(());
242    }
243    let raw_image_buffer = raw_image_buffer?;
244
245    let mut render_context: RenderContext = RenderContext::blank(lores_mode);
246    let _ = render_context.update();
247    render_context.clear();
248
249    match raw_image_buffer.colortype {
250        image::ColorType::Rgba8 => {
251            let img = image::ImageBuffer::<image::Rgba<u8>, Vec<u8>>::from_vec(
252                raw_image_buffer.dimensions.0 as u32,
253                raw_image_buffer.dimensions.1 as u32,
254                raw_image_buffer.buffer,
255            )
256            .ok_or("Cannot convert image data")?;
257
258            let resized_img = image::imageops::resize(
259                &img,
260                render_context.width as u32,
261                render_context.height as u32,
262                image::imageops::FilterType::Lanczos3,
263            );
264
265            for (count, pixel) in resized_img.pixels().enumerate() {
266                use image::Pixel;
267                let rgb = pixel.to_rgb();
268                render_context.frame_buffer[count] = (rgb[0], rgb[1], rgb[2]);
269            }
270        }
271        image::ColorType::Rgb8 => {
272            let img = image::ImageBuffer::<image::Rgb<u8>, Vec<u8>>::from_vec(
273                raw_image_buffer.dimensions.0 as u32,
274                raw_image_buffer.dimensions.1 as u32,
275                raw_image_buffer.buffer,
276            )
277            .ok_or("Cannot convert image data")?;
278
279            let resized_img = image::imageops::resize(
280                &img,
281                render_context.width as u32,
282                render_context.height as u32,
283                image::imageops::FilterType::Lanczos3,
284            );
285
286            for (count, pixel) in resized_img.pixels().enumerate() {
287                use image::Pixel;
288                let rgb = pixel.to_rgb();
289                render_context.frame_buffer[count] = (rgb[0], rgb[1], rgb[2]);
290            }
291        }
292        _ => {
293            //Not yet supported
294            outln!("{}", config_hex(&buffer, hex_config));
295            return Ok(());
296        }
297    }
298
299    render_context.flush();
300
301    let _ = std::io::stdout().execute(crossterm::cursor::Show);
302
303    let _ = crossterm::terminal::disable_raw_mode();
304
305    Ok(())
306}
307
308pub fn view_contents_interactive(
309    buffer: &[u8],
310    source: Option<&AnchorLocation>,
311    lores_mode: bool,
312) -> Result<(), Box<dyn std::error::Error>> {
313    use rawkey::{KeyCode, RawKey};
314
315    let sav_path = if let Some(AnchorLocation::File(f)) = source {
316        let mut path = std::path::PathBuf::from(f);
317        path.set_extension("sav");
318        Some(path)
319    } else {
320        None
321    };
322
323    let mut nes = neso::Nes::new(0.0);
324    let rawkey = RawKey::new();
325    nes.load_rom(buffer);
326
327    if let Some(ref sav_path) = sav_path {
328        if let Ok(contents) = std::fs::read(sav_path) {
329            let _ = nes.load_state(&contents);
330        }
331    }
332
333    nes.reset();
334
335    if let Ok(_raw) = crossterm::terminal::enable_raw_mode() {
336        let mut render_context: RenderContext = RenderContext::blank(lores_mode);
337
338        let buttons = vec![
339            KeyCode::Alt,
340            KeyCode::LeftControl,
341            KeyCode::Tab,
342            KeyCode::BackSpace,
343            KeyCode::UpArrow,
344            KeyCode::DownArrow,
345            KeyCode::LeftArrow,
346            KeyCode::RightArrow,
347        ];
348
349        let _ = std::io::stdout().execute(crossterm::cursor::Hide);
350
351        'gameloop: loop {
352            let _ = render_context.update();
353            nes.step_frame();
354
355            let image_buffer = nes.image_buffer();
356
357            let slice = unsafe { std::slice::from_raw_parts(image_buffer, 256 * 240 * 4) };
358            let img = image::ImageBuffer::<image::Rgba<u8>, &[u8]>::from_raw(256, 240, slice)
359                .ok_or("Cannot convert image data")?;
360            let resized_img = image::imageops::resize(
361                &img,
362                render_context.width as u32,
363                render_context.height as u32,
364                image::imageops::FilterType::Lanczos3,
365            );
366
367            render_context.clear();
368
369            for (count, pixel) in resized_img.pixels().enumerate() {
370                use image::Pixel;
371                let rgb = pixel.to_rgb();
372
373                render_context.frame_buffer[count] = (rgb[0], rgb[1], rgb[2]);
374            }
375            render_context.flush();
376
377            if rawkey.is_pressed(rawkey::KeyCode::Escape) {
378                break 'gameloop;
379            } else {
380                for (idx, button) in buttons.iter().enumerate() {
381                    if rawkey.is_pressed(*button) {
382                        nes.press_button(0, idx as u8);
383                    } else {
384                        nes.release_button(0, idx as u8);
385                    }
386                }
387                loop {
388                    let x = crossterm::event::poll(std::time::Duration::from_secs(0));
389                    match x {
390                        Ok(true) => {
391                            // Swallow the events so we don't queue them into the line editor
392                            let _ = crossterm::event::read();
393                        }
394                        _ => {
395                            break;
396                        }
397                    }
398                }
399            }
400        }
401    }
402
403    if let Some(ref sav_path) = sav_path {
404        let buffer = nes.save_state();
405        if let Ok(buffer) = buffer {
406            let _ = std::fs::write(sav_path, buffer);
407        }
408    }
409
410    let _ = std::io::stdout().execute(crossterm::cursor::Show);
411
412    let _screen = crossterm::terminal::disable_raw_mode();
413
414    Ok(())
415}