diff --git a/Cargo.lock b/Cargo.lock index ba7dba4..0fa3f86 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,19 +2,29 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "agon-cli-emulator" +version = "0.9.76" +dependencies = [ + "agon-ez80-emulator", + "agon-light-emulator-debugger", + "pico-args", +] + [[package]] name = "agon-ez80-emulator" -version = "0.9.75" +version = "0.9.76" dependencies = [ "chrono", "ez80", "filetime", + "pico-args", "rand", ] [[package]] name = "agon-light-emulator-debugger" -version = "0.9.75" +version = "0.9.76" dependencies = [ "agon-ez80-emulator", "ctrlc", @@ -196,7 +206,7 @@ source = "git+https://github.com/tomm/ez80.git?rev=64558c2acda1464248193f2f10804 [[package]] name = "fab-agon-emulator" -version = "0.9.75" +version = "0.9.76" dependencies = [ "agon-ez80-emulator", "agon-light-emulator-debugger", diff --git a/Cargo.toml b/Cargo.toml index 8dd973f..9fa973d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [workspace] -members = ["agon-light-emulator-debugger"] +members = ["agon-light-emulator-debugger", "agon-ez80-emulator", "agon-cli-emulator"] [workspace.package] -version = "0.9.75" +version = "0.9.76" edition = "2021" authors = ["Tom Morton "] license = "GPL-3.0" @@ -29,7 +29,7 @@ sdl2-sys = "*" sdl2 = "0.36.0" pico-args = "0.5.0" agon-ez80-emulator = { workspace = true } -agon-light-emulator-debugger = { path = "agon-light-emulator-debugger" } +agon-light-emulator-debugger = { workspace = true } libloading = "0.8.0" home = "0.5.9" serialport = "4.3.0" @@ -42,3 +42,5 @@ raw_tty = "0.1.0" [workspace.dependencies] agon-ez80-emulator = { path = "agon-ez80-emulator" } +agon-light-emulator-debugger = { path = "agon-light-emulator-debugger" } +agon-cli-emulator = { path = "agon-cli-emulator" } diff --git a/Makefile b/Makefile index ac0408c..f5d6cda 100644 --- a/Makefile +++ b/Makefile @@ -19,6 +19,8 @@ else endif cargo: + # First build agon-cli emulator + cargo build -r --manifest-path=./agon-cli-emulator/Cargo.toml ifeq ($(OS),Windows_NT) set FORCE=1 && cargo build -r cp ./target/release/fab-agon-emulator.exe . @@ -54,7 +56,8 @@ ifneq ($(shell ./fab-agon-emulator --prefix),) install -D -t $(shell ./fab-agon-emulator --prefix)/share/fab-agon-emulator/ firmware/vdp_*.so install -D -t $(shell ./fab-agon-emulator --prefix)/share/fab-agon-emulator/ firmware/mos_*.bin install -D -t $(shell ./fab-agon-emulator --prefix)/share/fab-agon-emulator/ firmware/mos_*.map - install -D -t $(shell ./fab-agon-emulator --prefix)/bin/ fab-agon-emulator + install -D -t $(shell ./fab-agon-emulator --prefix)/bin/ target/release/fab-agon-emulator + install -D -t $(shell ./fab-agon-emulator --prefix)/bin/ target/release/agon-cli-emulator else @echo "make install requires an install PREFIX (eg PREFIX=/usr/local make)" endif diff --git a/agon-cli-emulator/Cargo.toml b/agon-cli-emulator/Cargo.toml new file mode 100644 index 0000000..8e4e353 --- /dev/null +++ b/agon-cli-emulator/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "agon-cli-emulator" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true + +[dependencies] +agon-ez80-emulator = { workspace = true } +agon-light-emulator-debugger = { workspace = true } +pico-args = "0.5.0" + +#[target.'cfg(target_os = "linux")'.dependencies] +#raw_tty = "0.1.0" diff --git a/agon-ez80-emulator/src/bin/agon.rs b/agon-cli-emulator/src/main.rs similarity index 69% rename from agon-ez80-emulator/src/bin/agon.rs rename to agon-cli-emulator/src/main.rs index 0fbe6ef..42cc1ed 100644 --- a/agon-ez80-emulator/src/bin/agon.rs +++ b/agon-cli-emulator/src/main.rs @@ -1,3 +1,7 @@ +mod parse_args; +//use agon_ez80_emulator::debugger; +//use agon_ez80_emulator::debugger::{DebugCmd, DebugResp, DebuggerConnection, Trigger}; +use crate::parse_args::parse_args; use agon_ez80_emulator::{gpio, AgonMachine, AgonMachineConfig, RamInit, SerialLink}; use std::io::{self, BufRead, Write}; use std::sync::mpsc; @@ -49,7 +53,7 @@ fn handle_vdp( 0x11 => { rx_from_ez80.recv().unwrap(); } // color - v if v >= 0x20 && v != 0x7f => { + v if v == 8 || (v >= 0x20 && v != 0x7f) => { //print!("\x1b[0m{}\x1b[90m", char::from_u32(data as u32).unwrap()); print!("{}", char::from_u32(data as u32).unwrap()); } @@ -123,6 +127,7 @@ fn start_vdp( tx_vdp_to_ez80: Sender, rx_ez80_to_vdp: Receiver, gpios: std::sync::Arc>, + emulator_shutdown: std::sync::Arc, ) { let (tx_stdin, rx_stdin): (Sender, Receiver) = mpsc::channel(); @@ -139,7 +144,7 @@ fn start_vdp( let mut vdp_terminal_mode = false; let mut last_kb_input = std::time::Instant::now(); let mut last_vsync = std::time::Instant::now(); - loop { + while !emulator_shutdown.load(std::sync::atomic::Ordering::Relaxed) { if !handle_vdp(&tx_vdp_to_ez80, &rx_ez80_to_vdp, &mut vdp_terminal_mode) { // no packets from ez80. sleep a little std::thread::sleep(std::time::Duration::from_millis(1)); @@ -216,80 +221,120 @@ impl SerialLink for ChannelSerialLink { } } +const PREFIX: Option<&'static str> = option_env!("PREFIX"); + fn main() { + let args = match parse_args() { + Ok(a) => a, + Err(e) => { + eprintln!("Error parsing arguments: {}", e); + std::process::exit(-1); + } + }; + let (tx_vdp_to_ez80, from_vdp): (Sender, Receiver) = mpsc::channel(); let (to_vdp, rx_ez80_to_vdp): (Sender, Receiver) = mpsc::channel(); let soft_reset = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)); let emulator_shutdown = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)); - let gpios = std::sync::Arc::new(std::sync::Mutex::new(gpio::GpioSet::new())); let exit_status = std::sync::Arc::new(std::sync::atomic::AtomicI32::new(0)); + let gpios = std::sync::Arc::new(std::sync::Mutex::new(gpio::GpioSet::new())); let ez80_paused = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)); let gpios_ = gpios.clone(); - let mut unlimited_cpu = false; - let mut sdcard_img = None; - let mut args: Vec = std::env::args().skip(1).collect(); - while args.len() > 0 { - let arg = args.remove(0); - match arg.as_str() { - "-u" | "--unlimited-cpu" => { - unlimited_cpu = true; - } - "--sdcard-img" => { - sdcard_img = if args.len() > 0 { - Some(args.remove(0)) - } else { - None - }; - } - "--help" | "-h" | _ => { - println!("Usage: agon [OPTIONS]"); - println!(); - println!("Options:"); - println!(" --sdcard-img Use an SDcard image rather than hostfs"); - println!(" -u, --unlimited-cpu Don't limit CPU to Agon Light 18.432MHz"); - std::process::exit(0); - } - } - } + /* + let (tx_cmd_debugger, rx_cmd_debugger): (Sender, Receiver) = + mpsc::channel(); + let (tx_resp_debugger, rx_resp_debugger): (Sender, Receiver) = + mpsc::channel(); + */ - let _cpu_thread = std::thread::spawn(move || { - let _exit_status = exit_status.clone(); + let default_firmware = match PREFIX { + None => std::path::Path::new(".") + .join("firmware") + .join("mos_console8.bin"), + Some(prefix) => std::path::Path::new(prefix) + .join("share") + .join("fab-agon-emulator") + .join("mos_console8.bin"), + }; + + // Preserve stdin state, as debugger can leave stdin in raw mode + //#[cfg(target_os = "linux")] + //let _tty = raw_tty::TtyWithGuard::new(std::io::stdin()); + + let debugger_con = /*if args.debugger { let _ez80_paused = ez80_paused.clone(); - let mut machine = AgonMachine::new(AgonMachineConfig { - ram_init: RamInit::Random, - uart0_link: Box::new(ChannelSerialLink { - sender: to_vdp, - receiver: from_vdp, - }), - uart1_link: Box::new(DummySerialLink {}), - soft_reset, - exit_status: _exit_status, - paused: _ez80_paused, - emulator_shutdown, - gpios: gpios_, - clockspeed_hz: if unlimited_cpu { - std::u64::MAX - } else { - 18_432_000 - }, - mos_bin: std::path::PathBuf::from("../firmware/mos_console8.bin"), + let _emulator_shutdown = emulator_shutdown.clone(); + let _debugger_thread = std::thread::spawn(move || { + agon_light_emulator_debugger::start( + tx_cmd_debugger, + rx_resp_debugger, + _emulator_shutdown, + _ez80_paused.load(std::sync::atomic::Ordering::Relaxed), + ); }); + Some(DebuggerConnection { + tx: tx_resp_debugger, + rx: rx_cmd_debugger, + }) + } else*/ { + None + }; + + let _cpu_thread = { + let _exit_status = exit_status.clone(); + let _emulator_shutdown = emulator_shutdown.clone(); + std::thread::spawn(move || { + let _ez80_paused = ez80_paused.clone(); + let mut machine = AgonMachine::new(AgonMachineConfig { + ram_init: if args.zero { + RamInit::Zero + } else { + RamInit::Random + }, + uart0_link: Box::new(ChannelSerialLink { + sender: to_vdp, + receiver: from_vdp, + }), + uart1_link: Box::new(DummySerialLink {}), + soft_reset, + exit_status: _exit_status, + paused: _ez80_paused, + emulator_shutdown: _emulator_shutdown, + gpios: gpios_, + clockspeed_hz: if args.unlimited_cpu { + std::u64::MAX + } else { + 18_432_000 + }, + mos_bin: args.mos_bin.unwrap_or(default_firmware), + }); - if let Some(f) = sdcard_img { - match std::fs::File::options().read(true).write(true).open(&f) { - Ok(file) => machine.set_sdcard_image(Some(file)), - Err(e) => { - eprintln!("Could not open sdcard image '{}': {:?}", f, e); - std::process::exit(-1); + if let Some(f) = args.sdcard_img { + match std::fs::File::options().read(true).write(true).open(&f) { + Ok(file) => machine.set_sdcard_image(Some(file)), + Err(e) => { + eprintln!("Could not open sdcard image '{}': {:?}", f, e); + std::process::exit(-1); + } } + } else { + machine.set_sdcard_directory(match args.sdcard { + Some(dir) => std::path::PathBuf::from(dir), + None => std::env::current_dir().unwrap(), + }); } - } else { - machine.set_sdcard_directory(std::env::current_dir().unwrap().join("sdcard")); - } - machine.start(None); - }); + machine.start(debugger_con); + }); + }; + + start_vdp( + tx_vdp_to_ez80, + rx_ez80_to_vdp, + gpios, + emulator_shutdown.clone(), + ); - start_vdp(tx_vdp_to_ez80, rx_ez80_to_vdp, gpios); + std::process::exit(exit_status.load(std::sync::atomic::Ordering::Relaxed)); } diff --git a/agon-cli-emulator/src/parse_args.rs b/agon-cli-emulator/src/parse_args.rs new file mode 100644 index 0000000..926d432 --- /dev/null +++ b/agon-cli-emulator/src/parse_args.rs @@ -0,0 +1,55 @@ +const HELP: &str = "\ +Agon CLI Emulator + +USAGE: + agon-cli-emulator [OPTIONS] + +OPTIONS: + -h, --help Prints help information + --mos PATH Use a different MOS.bin firmware + --sdcard-img Use a raw SDCard image rather than the host filesystem + --sdcard Sets the path of the emulated SDCard + -u, --unlimited-cpu Don't limit eZ80 CPU frequency + -z, --zero Initialize ram with zeroes instead of random values +"; + +#[derive(Debug)] +pub struct AppArgs { + //pub debugger: bool, + pub sdcard: Option, + pub sdcard_img: Option, + pub unlimited_cpu: bool, + pub zero: bool, + pub mos_bin: Option, +} + +pub fn parse_args() -> Result { + let mut pargs = pico_args::Arguments::from_env(); + + // for `make install` + if pargs.contains("--prefix") { + print!("{}", option_env!("PREFIX").unwrap_or("")); + std::process::exit(0); + } + + if pargs.contains(["-h", "--help"]) { + print!("{}", HELP); + std::process::exit(0); + } + + let args = AppArgs { + //debugger: pargs.contains(["-d", "--debugger"]), + sdcard: pargs.opt_value_from_str("--sdcard")?, + sdcard_img: pargs.opt_value_from_str("--sdcard-img")?, + unlimited_cpu: pargs.contains(["-u", "--unlimited_cpu"]), + zero: pargs.contains(["-z", "--zero"]), + mos_bin: pargs.opt_value_from_str("--mos")?, + }; + + let remaining = pargs.finish(); + if !remaining.is_empty() { + eprintln!("Warning: unused arguments left: {:?}.", remaining); + } + + Ok(args) +} diff --git a/agon-ez80-emulator/Cargo.toml b/agon-ez80-emulator/Cargo.toml index 2918493..3b382cc 100644 --- a/agon-ez80-emulator/Cargo.toml +++ b/agon-ez80-emulator/Cargo.toml @@ -13,3 +13,4 @@ ez80 = { git = "https://github.com/tomm/ez80.git", rev="64558c2acda1464248193f2f rand = "*" filetime = "0.2" chrono = "0.4.38" +pico-args = "0.5.0" diff --git a/agon-ez80-emulator/src/debugger.rs b/agon-ez80-emulator/src/debugger.rs index b69e7ed..dabf182 100644 --- a/agon-ez80-emulator/src/debugger.rs +++ b/agon-ez80-emulator/src/debugger.rs @@ -140,7 +140,6 @@ impl DebuggerServer { machine.last_pc ))) .unwrap(); - self.send_disassembly(machine, cpu, None, machine.last_pc, machine.last_pc + 1); self.send_state(machine, cpu); } _ => {} diff --git a/dist_scripts/make-dist-linux.sh b/dist_scripts/make-dist-linux.sh index 924f74b..00b56ad 100755 --- a/dist_scripts/make-dist-linux.sh +++ b/dist_scripts/make-dist-linux.sh @@ -9,6 +9,7 @@ DIST_DIR=fab-agon-emulator-$VERSION-linux-$ARCH rm -rf $DIST_DIR mkdir $DIST_DIR cp ./target/release/fab-agon-emulator $DIST_DIR +cp ./target/release/agon-cli-emulator $DIST_DIR cp -r ./firmware $DIST_DIR cp LICENSE README.md $DIST_DIR mkdir $DIST_DIR/sdcard diff --git a/dist_scripts/make-dist-windows.sh b/dist_scripts/make-dist-windows.sh index e5d779b..466c657 100755 --- a/dist_scripts/make-dist-windows.sh +++ b/dist_scripts/make-dist-windows.sh @@ -31,6 +31,7 @@ DIST_DIR=fab-agon-emulator-$VERSION-windows-x64 rm -rf $DIST_DIR mkdir $DIST_DIR cp ./target/x86_64-pc-windows-gnu/release/fab-agon-emulator.exe $DIST_DIR +cp ./target/x86_64-pc-windows-gnu/release/agon-cli-emulator.exe $DIST_DIR cp -r ./firmware $DIST_DIR cp SDL2.dll $DIST_DIR cp libgcc_s_seh-1.dll $DIST_DIR diff --git a/sdcard b/sdcard index 62fde60..81672b7 160000 --- a/sdcard +++ b/sdcard @@ -1 +1 @@ -Subproject commit 62fde604a6ca0aa3d4235a446e3711c4470810bd +Subproject commit 81672b78be17a39d52019d8a8fcac49e77ca3a37