diff --git a/.cargo/config.toml b/.cargo/config.toml index 9bb778c..65b241f 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -3,3 +3,7 @@ coverage = "tarpaulin --out Html --skip-clean --output-dir coverage" [build] rustc-wrapper = "sccache" + +[target.x86_64-unknown-linux-gnu] +linker = "clang" +rustflags = ["-C", "link-arg=-fuse-ld=/usr/local/bin/mold"] diff --git a/Cargo.lock b/Cargo.lock index c08abba..c5c43ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "ab_glyph" @@ -1842,7 +1842,9 @@ dependencies = [ name = "gemmatelnet" version = "0.1.0" dependencies = [ + "clap", "gemma", + "log", ] [[package]] diff --git a/coverage/tarpaulin-report.html b/coverage/tarpaulin-report.html new file mode 100644 index 0000000..8d28d31 --- /dev/null +++ b/coverage/tarpaulin-report.html @@ -0,0 +1,671 @@ + + + + + + + +
+ + + + + + \ No newline at end of file diff --git a/gemma/src/chip8/computer.rs b/gemma/src/chip8/computer.rs index c34db82..ddee91d 100644 --- a/gemma/src/chip8/computer.rs +++ b/gemma/src/chip8/computer.rs @@ -92,6 +92,11 @@ impl Chip8Computer { self.registers.set_pc(offset); } + pub fn execute_instruction(&mut self, instruction_to_execute: Chip8CpuInstructions) { + // + } + + pub fn step_system(&mut self) -> &mut Chip8Computer { println!("Stepping System 1 Step"); // read the next instruction diff --git a/gemma/src/chip8/computer_manager.rs b/gemma/src/chip8/computer_manager.rs index 9cea84f..8174890 100644 --- a/gemma/src/chip8/computer_manager.rs +++ b/gemma/src/chip8/computer_manager.rs @@ -13,7 +13,7 @@ pub enum ManagerDumpables { } pub struct Chip8ComputerManager { - core_should_run: bool, + pub core_should_run: bool, one_step: bool, core_last_cycle_start: Instant, computer: Chip8Computer, @@ -31,6 +31,23 @@ impl Default for Chip8ComputerManager { } impl Chip8ComputerManager { + + /// Builds a string that can be displayed at a console giving the user + /// an idea of the internal state of the Chip-8 Computer + pub fn status_as_string(&self) -> String { + // build a string + format!("Should Run: {}\nLast Cycle Start: {:?}\nNum Cycles: {}\nRegisters\n{}\nTimers: {}/{}\nKeypad: \n{}\nVideo:\n{}", + self.core_should_run, + self.core_last_cycle_start, + self.computer.num_cycles, + self.computer.registers.format_as_string(), + self.computer.delay_timer.current(), + self.computer.sound_timer.current(), + self.computer.keypad.format_as_string(), + self.computer.video_memory.format_as_string() + ) + } + pub fn quirks_mode(&self) -> QuirkMode { self.computer.quirk_mode.clone() } diff --git a/gemma/src/chip8/instructions.rs b/gemma/src/chip8/instructions.rs index 08fd579..6dba5fd 100644 --- a/gemma/src/chip8/instructions.rs +++ b/gemma/src/chip8/instructions.rs @@ -7,7 +7,6 @@ use crate::constants::*; use log::debug; use rand::Rng; use serde::{Deserialize, Serialize}; -use std::ascii::AsciiExt; use std::fmt::{Debug, Display, Formatter}; use std::ops::BitAnd; use std::time::Instant; @@ -760,7 +759,7 @@ impl Chip8CpuInstructions { debug!("OrVxVy [0x{x:1x}] [0x{y:1x}]") } // 0x8xy2 Set Vx = Vx AND Vy - Chip8CpuInstructions::AND(x, y) => { + AND(x, y) => { let lhs_16 = input.registers.peek(*x) as u16; let rhs_16 = input.registers.peek(*y) as u16; // reset of VF quirk @@ -768,7 +767,7 @@ impl Chip8CpuInstructions { input.registers.poke(*x, (lhs_16 & rhs_16) as u8); } // 0x8xy3 Set Vx = Vx XOR Vy - Chip8CpuInstructions::ORY(x, y) => { + ORY(x, y) => { let lhs_16 = input.registers.peek(*x) as u16; let rhs_16 = input.registers.peek(*y) as u16; // reset of VF quirk @@ -776,7 +775,7 @@ impl Chip8CpuInstructions { input.registers.poke(*x, (lhs_16 ^ rhs_16) as u8); } // 0x8xy4 Set Vx = Vx + Vy (SET VF on Carry) - Chip8CpuInstructions::ADDR(x, y) => { + ADDR(x, y) => { let lhs = input.registers.peek(*x) as i16; let rhs = input.registers.peek(*y) as i16; let working = lhs + rhs; @@ -788,7 +787,7 @@ impl Chip8CpuInstructions { input.registers.poke(0x0f, 0x00); } } - Chip8CpuInstructions::SUB(x, y) => { + SUB(x, y) => { // 8xy5 - SUB Vx, Vy // Set Vx = Vx - Vy, set VF = NOT borrow. // diff --git a/gemma/src/chip8/system_memory.rs b/gemma/src/chip8/system_memory.rs index 5a68a44..97c197e 100644 --- a/gemma/src/chip8/system_memory.rs +++ b/gemma/src/chip8/system_memory.rs @@ -4,7 +4,6 @@ use crate::constants::*; use serde::Deserialize; use serde::Serialize; -use super::quirk_modes; use super::quirk_modes::QuirkMode; #[derive(Clone, Serialize, Deserialize, Debug)] diff --git a/gemma/src/chip8/video.rs b/gemma/src/chip8/video.rs index 146d735..80d295b 100644 --- a/gemma/src/chip8/video.rs +++ b/gemma/src/chip8/video.rs @@ -49,7 +49,7 @@ impl Chip8Video { LowRes => CHIP8_VIDEO_MEMORY, HighRes => SCHIP_VIDE_MEMORY, }; - for i in 0..num_loops { + for _ in 0..num_loops { self.memory.push(false); } } @@ -115,7 +115,7 @@ impl Chip8Video { pub fn format_as_string(&self) -> String { let (width, height) = self.get_resolution(); - println!("FORMATTING {width}x{height}"); + // println!("FORMATTING {width}x{height}"); let mut output = String::new(); for row in 0..height { for column in 0..width { @@ -177,7 +177,7 @@ impl Chip8Video { for current_row in 0..height { let row_offset = current_row * width; - for current_column in (0..width - 4) { + for current_column in 0..width - 4 { let target: usize = (row_offset + current_column) as usize; let source: usize = target + 4; self.memory[target] = self.memory[source]; diff --git a/gemma/src/chip8_2/instructions/add.rs b/gemma/src/chip8_2/instructions/add.rs new file mode 100644 index 0000000..ac86548 --- /dev/null +++ b/gemma/src/chip8_2/instructions/add.rs @@ -0,0 +1,23 @@ +use crate::chip8::instructions::Chip8CpuInstructions; +use crate::chip8::registers::Chip8Registers; +use crate::chip8_2::instructions::instruction_trait::InstructionTrait; + +struct Add { + pub relative: bool, + pub source: Option, + pub destination: Chip8Registers, + pub literal: Option +} + +impl InstructionTrait for Add { + fn length(&self) -> u8 { + 1 + } + + fn encoded(&self) -> Chip8CpuInstructions { + } + + fn decode(from: Vec) -> &dyn InstructionTrait { + todo!() + } +} \ No newline at end of file diff --git a/gemma/src/chip8_2/instructions/call.rs b/gemma/src/chip8_2/instructions/call.rs new file mode 100644 index 0000000..e69de29 diff --git a/gemma/src/chip8_2/instructions/cls.rs b/gemma/src/chip8_2/instructions/cls.rs new file mode 100644 index 0000000..e69de29 diff --git a/gemma/src/chip8_2/instructions/drw.rs b/gemma/src/chip8_2/instructions/drw.rs new file mode 100644 index 0000000..e69de29 diff --git a/gemma/src/chip8_2/instructions/exit.rs b/gemma/src/chip8_2/instructions/exit.rs new file mode 100644 index 0000000..e69de29 diff --git a/gemma/src/chip8_2/instructions/high.rs b/gemma/src/chip8_2/instructions/high.rs new file mode 100644 index 0000000..e69de29 diff --git a/gemma/src/chip8_2/instructions/jp.rs b/gemma/src/chip8_2/instructions/jp.rs new file mode 100644 index 0000000..e69de29 diff --git a/gemma/src/chip8_2/instructions/ld.rs b/gemma/src/chip8_2/instructions/ld.rs new file mode 100644 index 0000000..e69de29 diff --git a/gemma/src/chip8_2/instructions/low.rs b/gemma/src/chip8_2/instructions/low.rs new file mode 100644 index 0000000..e69de29 diff --git a/gemma/src/chip8_2/instructions/ret.rs b/gemma/src/chip8_2/instructions/ret.rs new file mode 100644 index 0000000..e69de29 diff --git a/gemma/src/chip8_2/instructions/rnd.rs b/gemma/src/chip8_2/instructions/rnd.rs new file mode 100644 index 0000000..e69de29 diff --git a/gemma/src/chip8_2/instructions/scd.rs b/gemma/src/chip8_2/instructions/scd.rs new file mode 100644 index 0000000..e69de29 diff --git a/gemma/src/chip8_2/instructions/scl.rs b/gemma/src/chip8_2/instructions/scl.rs new file mode 100644 index 0000000..e69de29 diff --git a/gemma/src/chip8_2/instructions/scr.rs b/gemma/src/chip8_2/instructions/scr.rs new file mode 100644 index 0000000..e69de29 diff --git a/gemma/src/chip8_2/instructions/se.rs b/gemma/src/chip8_2/instructions/se.rs new file mode 100644 index 0000000..e69de29 diff --git a/gemma/src/chip8_2/instructions/sknp.rs b/gemma/src/chip8_2/instructions/sknp.rs new file mode 100644 index 0000000..e69de29 diff --git a/gemma/src/chip8_2/instructions/skp.rs b/gemma/src/chip8_2/instructions/skp.rs new file mode 100644 index 0000000..e69de29 diff --git a/gemma/src/chip8_2/instructions/sne.rs b/gemma/src/chip8_2/instructions/sne.rs new file mode 100644 index 0000000..e69de29 diff --git a/gemma/src/chip8_2/instructions/sys.rs b/gemma/src/chip8_2/instructions/sys.rs new file mode 100644 index 0000000..e69de29 diff --git a/gemmatelnet/Cargo.toml b/gemmatelnet/Cargo.toml index 41dd034..9533577 100644 --- a/gemmatelnet/Cargo.toml +++ b/gemmatelnet/Cargo.toml @@ -1,7 +1,9 @@ [package] name = "gemmatelnet" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] gemma = { path = "../gemma" } +log = "0.4.22" +clap = { version = "4.5.20", features = ["derive"] } diff --git a/gemmatelnet/src/bin/gemmatelnetd.rs b/gemmatelnet/src/bin/gemmatelnetd.rs index 58e3f33..986466f 100644 --- a/gemmatelnet/src/bin/gemmatelnetd.rs +++ b/gemmatelnet/src/bin/gemmatelnetd.rs @@ -1,3 +1,50 @@ -fn main() { - println!("Taxation is Theft"); +use std::io::{BufRead, BufReader, Write}; +use std::net::{TcpListener, TcpStream}; +use std::thread; +use gemma::chip8::computer_manager::Chip8ComputerManager; +use gemma::chip8::quirk_modes::QuirkMode; +use gemmatelnet::client_handler::handle_client; +use log::info; +use clap::Parser; + +const LISTEN_HOST: &str = "0.0.0.0"; +const LISTEN_PORT: u32 = 1420; + +#[derive(Parser, Debug)] +#[command(name = "myapp")] +#[command(about = "Does awesome things", long_about = None)] +struct Cli { + #[arg(long, default_value = "0.0.0.0")] + host: Option, + #[arg(long, default_value = "1420")] + port: Option, +} + +impl Cli { + pub fn host(&self) -> String { + self.host.clone().unwrap_or(LISTEN_HOST.into()) + } + pub fn port(&self) -> u32 { + self.port.unwrap_or(LISTEN_PORT.into()) + } +} + +fn main() -> std::io::Result<()> { + let config = Cli::parse(); + + let listener = TcpListener::bind(format!("{}:{}", config.host(), config.port()))?; + println!("Listening to {}:{}", LISTEN_HOST, LISTEN_PORT); + + for stream in listener.incoming() { + match stream { + Ok(stream) => { + thread::spawn(|| handle_client(stream)); + } + Err(e) => { + eprintln!("Connection Failed -> {}", e); + } + } + } + + Ok(()) } \ No newline at end of file diff --git a/gemmatelnet/src/client_handler.rs b/gemmatelnet/src/client_handler.rs new file mode 100644 index 0000000..f1b53fa --- /dev/null +++ b/gemmatelnet/src/client_handler.rs @@ -0,0 +1,127 @@ +use std::io::{BufRead, BufReader}; +use std::net::TcpStream; +use gemma::chip8::computer_manager::Chip8ComputerManager; +use gemma::chip8::quirk_modes::QuirkMode; +use crate::telnet_utils::list_of_files_in_directory; +use std::io::Write; +use std::path::Path; + +const CANT_NOTIFY_ERROR: &str = "Unable to notify client"; +const MENU_TEXT: &str = r#" +--------------------------- +status -> Current status of the Instance +new -> Start New Instance +* list -> list available files to load into Chip-8 Instance +* load -> Load Chip-8 Program from server +menu -> display the menu +step -> step chip-8 machine 1 step +* steps -> Step a fixed number of steps +* memory -> dump current memory from chip-8 instance +* video -> dump current video from chip-8 instance +* window -> display a window of memory +disconnect -> close connection +--------------------------- +"#; + +pub fn handle_client(mut stream: TcpStream) { + // each client gets their own instance of a Chip-8 system + let mut chip8 = Chip8ComputerManager::default(); + + let peer_addr = stream.peer_addr().unwrap(); + println!("New CX From {}", peer_addr); + + let mut reader = BufReader::new(stream.try_clone().unwrap()); + let mut line = String::new(); + + write!(stream, "Welcome to Gemma Telnet Server\n").unwrap(); + write!(stream, "{}", MENU_TEXT).unwrap(); + + let mut time_to_die: bool = false; + + while !time_to_die { + + // clear the buffer + line.clear(); + + // read from the client to the buffer + let bytes = reader.read_line(&mut line).unwrap(); + if bytes == 0 { + println!("Connection closed by {}", peer_addr); + break; + } + + // display the message received + eprint!("RX {}: {}", peer_addr, line); + + // split the line into (if params exist) + + let mut result = line.split(" "); + let command = result.next().unwrap(); + let p1 = result.next(); + let p2 = result.next(); + + let mut message_to_send: String = "Unrecognized Command".to_string(); + + match command.trim() { + "status" => { + message_to_send = chip8.status_as_string(); + } + "disconnect" => { + message_to_send = "Disconnecting User".to_string(); + time_to_die = true; + break; + } + "new" => { + message_to_send = "Starting new Chip-8 Instance".to_string(); + chip8.reset(QuirkMode::Chip8); + } + "list" => { + message_to_send = + format!("Listing files in path: \n{}", + list_of_files_in_directory( + &mut Path::new("/home/tmerritt/Projects/trevors_chip8_toy/resources/roms".into()) + ) + ); + } + "load" => { + + // chip8 = Chip8ComputerManager::from(chip8).load_new_program_to_system_memory() + message_to_send = format!("Preparing to load {} from disk.", + p1.unwrap_or("default.ch8").trim()); + chip8.start(); + } + "menu" => { + message_to_send = MENU_TEXT.to_string(); + } + "steps" => { + if chip8.core_should_run { + let num_steps = p1.unwrap_or("0").trim(); + message_to_send = format!("Advancing {} steps.", num_steps); + for _ in 0..num_steps.parse().unwrap_or(0) { + chip8.tick(); + } + } else { + message_to_send = "Not Ready to Step".to_string(); + } + } + "step" => { + if chip8.core_should_run { + message_to_send = "Advancing 1 step.".to_string(); + chip8.tick(); + } else { + message_to_send = "Not Ready To Step".to_string(); + } + } + "window" => { + message_to_send = format!("Memory window from {} to {}", + p1.unwrap_or("0x0000").trim(), + p2.unwrap_or("0xFFFF").trim()); + } + _ => {} + } + + write!(stream, "{}\n", message_to_send).expect(CANT_NOTIFY_ERROR); + + stream.flush().unwrap(); + } +} diff --git a/gemmatelnet/src/lib.rs b/gemmatelnet/src/lib.rs new file mode 100644 index 0000000..5b3a199 --- /dev/null +++ b/gemmatelnet/src/lib.rs @@ -0,0 +1,3 @@ + +pub mod telnet_utils; +pub mod client_handler; \ No newline at end of file diff --git a/gemmatelnet/src/telnet_utils.rs b/gemmatelnet/src/telnet_utils.rs new file mode 100644 index 0000000..89ef167 --- /dev/null +++ b/gemmatelnet/src/telnet_utils.rs @@ -0,0 +1,18 @@ +use std::{fs, io}; +use std::path::Path; + +pub fn list_of_files_in_directory(root_directory: &Path) -> String { + + let mut return_value = String::new(); + + if root_directory.is_dir() { + for entry in fs::read_dir(root_directory).unwrap() { + let entry = entry.unwrap(); + let path = entry.path(); + if path.is_file() { + return_value = format!("{}\n{}", return_value, path.file_name().unwrap().to_string_lossy()); + } + } + } + return_value +} \ No newline at end of file diff --git a/gemmautil/src/bin/ch8asm.rs b/gemmautil/src/bin/ch8asm.rs index 914ecd4..e94e816 100644 --- a/gemmautil/src/bin/ch8asm.rs +++ b/gemmautil/src/bin/ch8asm.rs @@ -11,7 +11,6 @@ use std::io::Write; // <0x|0X>[, ][<0x|OX>[, ][<0x|OX>]][; comment] use pest::Parser; use pest_derive::Parser; - #[derive(Parser)] #[grammar = "chip8_asm.pest"] pub struct Chip8AsmParser; diff --git a/gemmautil/src/bin/ch8disasm.rs b/gemmautil/src/bin/ch8disasm.rs index 3c90408..d52be59 100644 --- a/gemmautil/src/bin/ch8disasm.rs +++ b/gemmautil/src/bin/ch8disasm.rs @@ -68,7 +68,6 @@ fn main() { std::fs::read(source_file).unwrap() ); - match result.output_file { None => { println!("Output to console.");