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<ScreenDimensions> {
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<u8> {
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}'"),
}
}
}