use log::*; use crate::*; use bedrock_pc::*; use phosphor::*; use std::cmp::{min, max}; use std::num::NonZeroU32; pub fn main(mut args: Switchboard) { args.positional("source"); args.named("debug").short('d'); args.named("mode").default("dynamic"); args.named("palette"); args.named("fullscreen").short('f'); args.named("show-cursor").short('c'); args.named("zoom").short('z').quick("3").default("1"); args.named("size").short('s'); args.named("decode-stdin").short('i'); args.named("encode-stdout").short('o'); args.raise_errors(); let source = args.get("source").as_path_opt(); let debug = args.get("debug").as_bool(); let mode = Mode::from_str(args.get("mode").as_str()); let override_palette = args.get("palette").as_str_opt().map(parse_palette); let fullscreen = args.get("fullscreen").as_bool(); let show_cursor = args.get("show-cursor").as_bool(); let zoom_raw = min(10, max(1, args.get("zoom").as_u32())); let zoom = unsafe { NonZeroU32::new_unchecked(zoom_raw) }; let initial_dimensions = match args.get("size").as_str_opt() { Some(string) => parse_dimensions(string), None => DEFAULT_SCREEN_SIZE / (zoom_raw as u16), }; let decode_stdin = args.get("decode-stdin").as_bool(); let encode_stdout = args.get("encode-stdout").as_bool(); // ----------------------------------------------------------------------- let Program { bytecode, path } = load_program(source.as_ref()); let symbols_path = path.as_ref().map(|p| { let mut path = p.to_path_buf(); path.add_extension("sym"); path }); let metadata = parse_metadata(&bytecode); if metadata.is_none() { info!("Could not read program metadata"); } let config = EmulatorConfig { initial_dimensions, fullscreen, zoom, override_palette, show_cursor, decode_stdin, encode_stdout, symbols_path, metadata, }; if let Ok(phosphor) = Phosphor::new() { match mode { Mode::Dynamic => { info!("Starting dynamic emulator"); let mut emulator = DynamicEmulator::new(&config, debug); emulator.load_program(&bytecode); emulator.run(); info!("Upgrading dynamic emulator to graphical emulator"); let emulator = emulator.to_graphical(config); emulator.run(phosphor); } Mode::Graphical => { info!("Starting graphical emulator"); let emulator = GraphicalEmulator::new(config, debug); emulator.run(phosphor); } Mode::Headless => { info!("Starting headless emulator"); let mut emulator = HeadlessEmulator::new(&config, debug); emulator.run(); } } } else { match mode { Mode::Dynamic => { info!("Could not start graphical event loop"); info!("Starting headless emulator"); let mut emulator = HeadlessEmulator::new(&config, debug); emulator.run(); } Mode::Graphical => { fatal!("Could not start graphical event loop"); } Mode::Headless => { info!("Starting headless emulator"); let mut emulator = HeadlessEmulator::new(&config, debug); emulator.run(); } } } } fn parse_dimensions(string: &str) -> ScreenDimensions { fn parse_inner(string: &str) -> Option { let (w_str, h_str) = string.trim().split_once('x')?; Some( ScreenDimensions { width: w_str.parse().ok()?, height: h_str.parse().ok()?, } ) } let dimensions = parse_inner(string).unwrap_or_else(|| { fatal!("Invalid dimensions string '{string}'"); }); if dimensions.is_zero() { fatal!("Screen dimensions must be greater than zero"); } return dimensions; } fn parse_palette(string: &str) -> [Colour; 16] { fn decode_ascii_hex_digit(ascii: u8) -> Option { match ascii { b'0'..=b'9' => Some(ascii - b'0'), b'a'..=b'f' => Some(ascii - b'a' + 10), b'A'..=b'F' => Some(ascii - b'A' + 10), _ => { None } } } fn parse_inner(string: &str) -> Option<[Colour; 16]> { let mut c = Vec::new(); for token in string.split(',') { let mut bytes = token.bytes(); if bytes.len() != 3 { return None; } let r = decode_ascii_hex_digit(bytes.next().unwrap())?; let g = decode_ascii_hex_digit(bytes.next().unwrap())?; let b = decode_ascii_hex_digit(bytes.next().unwrap())?; c.push(Colour::from_rgb(r*17, g*17, b*17)); } Some(match c.len() { 2 => [ c[0], c[1], c[0], c[1], c[0], c[1], c[0], c[1], c[0], c[1], c[0], c[1], c[0], c[1], c[0], c[1] ], 3 => [ c[0], c[1], c[0], c[2], c[0], c[1], c[0], c[2], c[0], c[1], c[0], c[2], c[0], c[1], c[0], c[2] ], 4 => [ c[0], c[1], c[2], c[3], c[0], c[1], c[2], c[3], c[0], c[1], c[2], c[3], c[0], c[1], c[2], c[3] ], 8 => [ c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7], c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7] ], 16 => [ c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7], c[8], c[9], c[10], c[11], c[12], c[13], c[14], c[15] ], _ => return None, }) } parse_inner(string).unwrap_or_else(|| { fatal!("Invalid palette string '{string}'"); }) } #[derive(Copy, Clone, PartialEq)] enum Mode { Graphical, Headless, Dynamic, } impl Mode { pub fn from_str(string: &str) -> Self { match string { "g" | "graphical" => Self::Graphical, "h" | "headless" => Self::Headless, "d" | "dynamic" => Self::Dynamic, _ => fatal!("Invalid mode string '{string}'"), } } }