summaryrefslogtreecommitdiff
path: root/src/bin/br/run.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin/br/run.rs')
-rw-r--r--src/bin/br/run.rs186
1 files changed, 186 insertions, 0 deletions
diff --git a/src/bin/br/run.rs b/src/bin/br/run.rs
new file mode 100644
index 0000000..86fdd43
--- /dev/null
+++ b/src/bin/br/run.rs
@@ -0,0 +1,186 @@
+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}'"),
+ }
+ }
+}