use crossterm::{style::Attribute, ExecutableCommand}; use nu_errors::ShellError; use nu_plugin::{serve_plugin, Plugin}; use nu_protocol::{outln, CallInfo, Primitive, Signature, UntaggedValue, Value}; use nu_source::AnchorLocation; use pretty_hex::*; struct BinaryView; impl BinaryView { fn new() -> BinaryView { BinaryView } } impl Plugin for BinaryView { fn config(&mut self) -> Result { Ok(Signature::build("binaryview") .desc("Autoview of binary data.") .switch("lores", "use low resolution output mode")) } fn sink(&mut self, call_info: CallInfo, input: Vec) { for v in input { let value_anchor = v.anchor(); if let UntaggedValue::Primitive(Primitive::Binary(b)) = &v.value { let _ = view_binary(&b, value_anchor.as_ref(), call_info.args.has("lores")); } } } } fn view_binary( b: &[u8], source: Option<&AnchorLocation>, lores_mode: bool, ) -> Result<(), Box> { if b.len() > 3 { if let (0x4e, 0x45, 0x53) = (b[0], b[1], b[2]) { view_contents_interactive(b, source, lores_mode)?; return Ok(()); } } view_contents(b, source, lores_mode)?; Ok(()) } pub struct RenderContext { pub width: usize, pub height: usize, pub frame_buffer: Vec<(u8, u8, u8)>, pub since_last_button: Vec, pub lores_mode: bool, } impl RenderContext { pub fn blank(lores_mode: bool) -> RenderContext { RenderContext { width: 0, height: 0, frame_buffer: vec![], since_last_button: vec![0; 8], lores_mode, } } pub fn clear(&mut self) { self.frame_buffer = vec![(0, 0, 0); self.width * self.height as usize]; } fn render_to_screen_lores(&mut self) -> Result<(), Box> { let mut prev_color: Option<(u8, u8, u8)> = None; let mut prev_count = 1; let _ = std::io::stdout().execute(crossterm::cursor::MoveTo(0, 0)); for pixel in &self.frame_buffer { match prev_color { Some(c) if c == *pixel => { prev_count += 1; } Some(c) => { print!( "{}", ansi_term::Colour::RGB(c.0, c.1, c.2) .paint((0..prev_count).map(|_| "█").collect::()) ); prev_color = Some(*pixel); prev_count = 1; } _ => { prev_color = Some(*pixel); prev_count = 1; } } } if prev_count > 0 { if let Some(color) = prev_color { print!( "{}", ansi_term::Colour::RGB(color.0, color.1, color.2) .paint((0..prev_count).map(|_| "█").collect::()) ); } } outln!("{}", Attribute::Reset); Ok(()) } fn render_to_screen_hires(&mut self) -> Result<(), Box> { let mut prev_fg: Option<(u8, u8, u8)> = None; let mut prev_bg: Option<(u8, u8, u8)> = None; let mut prev_count = 1; let mut pos = 0; let fb_len = self.frame_buffer.len(); let _ = std::io::stdout().execute(crossterm::cursor::MoveTo(0, 0)); while pos < (fb_len - self.width) { let top_pixel = self.frame_buffer[pos]; let bottom_pixel = self.frame_buffer[pos + self.width]; match (prev_fg, prev_bg) { (Some(c), Some(d)) if c == top_pixel && d == bottom_pixel => { prev_count += 1; } (Some(c), Some(d)) => { print!( "{}", ansi_term::Colour::RGB(c.0, c.1, c.2) .on(ansi_term::Colour::RGB(d.0, d.1, d.2,)) .paint((0..prev_count).map(|_| "▀").collect::()) ); prev_fg = Some(top_pixel); prev_bg = Some(bottom_pixel); prev_count = 1; } _ => { prev_fg = Some(top_pixel); prev_bg = Some(bottom_pixel); prev_count = 1; } } pos += 1; if pos % self.width == 0 { pos += self.width; } } if prev_count > 0 { if let (Some(c), Some(d)) = (prev_fg, prev_bg) { print!( "{}", ansi_term::Colour::RGB(c.0, c.1, c.2) .on(ansi_term::Colour::RGB(d.0, d.1, d.2,)) .paint((0..prev_count).map(|_| "▀").collect::()) ); } } outln!("{}", Attribute::Reset); Ok(()) } pub fn flush(&mut self) -> Result<(), Box> { if self.lores_mode { self.render_to_screen_lores() } else { self.render_to_screen_hires() } } pub fn update(&mut self) -> Result<(), Box> { let terminal_size = crossterm::terminal::size().unwrap_or_else(|_| (80, 24)); if (self.width != terminal_size.0 as usize) || (self.height != terminal_size.1 as usize) { let _ = std::io::stdout().execute(crossterm::cursor::Hide); self.width = terminal_size.0 as usize; self.height = if self.lores_mode { terminal_size.1 as usize - 1 } else { (terminal_size.1 as usize - 1) * 2 }; } Ok(()) } } #[derive(Debug)] struct RawImageBuffer { dimensions: (u64, u64), colortype: image::ColorType, buffer: Vec, } fn load_from_png_buffer(buffer: &[u8]) -> Result> { use image::ImageDecoder; let decoder = image::png::PNGDecoder::new(buffer)?; let dimensions = decoder.dimensions(); let colortype = decoder.colortype(); let buffer = decoder.read_image()?; Ok(RawImageBuffer { dimensions, colortype, buffer, }) } fn load_from_jpg_buffer(buffer: &[u8]) -> Result> { use image::ImageDecoder; let decoder = image::jpeg::JPEGDecoder::new(buffer)?; let dimensions = decoder.dimensions(); let colortype = decoder.colortype(); let buffer = decoder.read_image()?; Ok(RawImageBuffer { dimensions, colortype, buffer, }) } pub fn view_contents( buffer: &[u8], _source: Option<&AnchorLocation>, lores_mode: bool, ) -> Result<(), Box> { let mut raw_image_buffer = load_from_png_buffer(buffer); if raw_image_buffer.is_err() { raw_image_buffer = load_from_jpg_buffer(buffer); } if raw_image_buffer.is_err() { //Not yet supported outln!("{:?}", buffer.hex_dump()); return Ok(()); } let raw_image_buffer = raw_image_buffer?; let mut render_context: RenderContext = RenderContext::blank(lores_mode); let _ = render_context.update(); render_context.clear(); match raw_image_buffer.colortype { image::ColorType::RGBA(8) => { let img = image::ImageBuffer::, Vec>::from_vec( raw_image_buffer.dimensions.0 as u32, raw_image_buffer.dimensions.1 as u32, raw_image_buffer.buffer, ) .ok_or("Cannot convert image data")?; let resized_img = image::imageops::resize( &img, render_context.width as u32, render_context.height as u32, image::FilterType::Lanczos3, ); let mut count = 0; for pixel in resized_img.pixels() { use image::Pixel; let rgb = pixel.to_rgb(); render_context.frame_buffer[count] = (rgb[0], rgb[1], rgb[2]); count += 1; } } image::ColorType::RGB(8) => { let img = image::ImageBuffer::, Vec>::from_vec( raw_image_buffer.dimensions.0 as u32, raw_image_buffer.dimensions.1 as u32, raw_image_buffer.buffer, ) .ok_or("Cannot convert image data")?; let resized_img = image::imageops::resize( &img, render_context.width as u32, render_context.height as u32, image::FilterType::Lanczos3, ); let mut count = 0; for pixel in resized_img.pixels() { use image::Pixel; let rgb = pixel.to_rgb(); render_context.frame_buffer[count] = (rgb[0], rgb[1], rgb[2]); count += 1; } } _ => { //Not yet supported outln!("{:?}", buffer.hex_dump()); return Ok(()); } } render_context.flush()?; let _ = std::io::stdout().execute(crossterm::cursor::Show); let _ = crossterm::terminal::disable_raw_mode(); Ok(()) } pub fn view_contents_interactive( buffer: &[u8], source: Option<&AnchorLocation>, lores_mode: bool, ) -> Result<(), Box> { use rawkey::{KeyCode, RawKey}; let sav_path = if let Some(AnchorLocation::File(f)) = source { let mut path = std::path::PathBuf::from(f); path.set_extension("sav"); Some(path) } else { None }; let mut nes = neso::Nes::new(0.0); let rawkey = RawKey::new(); nes.load_rom(&buffer); if let Some(ref sav_path) = sav_path { if let Ok(contents) = std::fs::read(sav_path) { let _ = nes.load_state(&contents); } } nes.reset(); if let Ok(_raw) = crossterm::terminal::enable_raw_mode() { let mut render_context: RenderContext = RenderContext::blank(lores_mode); let buttons = vec![ KeyCode::Alt, KeyCode::LeftControl, KeyCode::Tab, KeyCode::BackSpace, KeyCode::UpArrow, KeyCode::DownArrow, KeyCode::LeftArrow, KeyCode::RightArrow, ]; let _ = std::io::stdout().execute(crossterm::cursor::Hide); 'gameloop: loop { let _ = render_context.update(); nes.step_frame(); let image_buffer = nes.image_buffer(); let slice = unsafe { std::slice::from_raw_parts(image_buffer, 256 * 240 * 4) }; let img = image::ImageBuffer::, &[u8]>::from_raw(256, 240, slice) .ok_or("Cannot convert image data")?; let resized_img = image::imageops::resize( &img, render_context.width as u32, render_context.height as u32, image::FilterType::Lanczos3, ); render_context.clear(); let mut count = 0; for pixel in resized_img.pixels() { use image::Pixel; let rgb = pixel.to_rgb(); render_context.frame_buffer[count] = (rgb[0], rgb[1], rgb[2]); count += 1; } render_context.flush()?; if rawkey.is_pressed(rawkey::KeyCode::Escape) { break 'gameloop; } else { for (idx, button) in buttons.iter().enumerate() { if rawkey.is_pressed(*button) { nes.press_button(0, idx as u8); } else { nes.release_button(0, idx as u8); } } loop { let x = crossterm::event::poll(std::time::Duration::from_secs(0)); match x { Ok(true) => { // Swallow the events so we don't queue them into the line editor let _ = crossterm::event::read(); } _ => { break; } } } } } } if let Some(ref sav_path) = sav_path { let buffer = nes.save_state(); if let Ok(buffer) = buffer { let _ = std::fs::write(sav_path, buffer); } } let _ = std::io::stdout().execute(crossterm::cursor::Show); let _screen = crossterm::terminal::disable_raw_mode(); Ok(()) } fn main() { serve_plugin(&mut BinaryView::new()); }