fixes reset from exection completion

adds 'default.oc8'
adds control window size to state for load/save of settings maybe?
moves TestCompressionTool code into InstructionUtil
remove gemmautil and moves into gemma
This commit is contained in:
Trevor Merritt 2024-11-08 09:42:28 -05:00
parent 67ca71ccb7
commit cddbe0c46e
41 changed files with 464 additions and 310 deletions

12
Cargo.lock generated
View File

@ -1789,6 +1789,8 @@ dependencies = [
"flate2",
"hex",
"log",
"pest",
"pest_derive",
"pretty_env_logger",
"rand 0.9.0-alpha.2",
"serde",
@ -1845,16 +1847,6 @@ dependencies = [
"gemma",
]
[[package]]
name = "gemmautil"
version = "0.1.0"
dependencies = [
"clap",
"gemma",
"pest",
"pest_derive",
]
[[package]]
name = "generic-array"
version = "0.14.7"

View File

@ -5,7 +5,6 @@ members = [
"gemmaimgui",
"gemmatelnet",
"gemmasdl2",
"gemmautil",
]
resolver = "2"

View File

@ -16,3 +16,5 @@ serde_json.workspace = true
flate2 = "1.0"
clap = { version = "4.5.20", features = ["derive"] }
hex = "0.4.3"
pest = { version = "2.7.14" }
pest_derive = { version = "2.7.14"}

View File

@ -2,6 +2,7 @@ use gemma::chip8::util::InstructionUtil;
use gemma::chip8::{instructions::Chip8CpuInstructions, quirk_modes::QuirkMode};
use std::fs::{self, File};
use std::io::Write;
use clap::{Arg, Command};
// Ch8Asm
// Converts well formed CH8ASM.
// no variables.
@ -16,9 +17,26 @@ use pest_derive::Parser;
#[grammar = "chip8_asm.pest"]
pub struct Chip8AsmParser;
fn main() {
println!("Taxation is Theft");
let matches = Command::new("Chip8Assembler")
.version("0.0.1")
.author("Trevor Merritt <trevor.merritt@geekback.dev>")
.about("Tool to assemble simple ASM into Chip8 Machine code")
.arg(
Arg::new("input-file")
.help("Name of file to assemble")
.required(true)
)
.arg(
Arg::new("output-file")
.help("Name of file to create")
.required(false)
)
.get_matches();
let unparsed = fs::read_to_string("resources/test/gemma_disassembler_manual_document.asc")
.expect("Unable to read input");

View File

@ -1,57 +0,0 @@
use clap::{Parser, Subcommand};
use flate2::{write::{GzEncoder, GzDecoder}, Compression};
use std::io::prelude::*;
use std::io::{self, Write};
#[derive(Parser)]
#[command(author, version, about = "Compress or decompress a string", long_about = None)]
struct Cli {
/// Subcommand: either 'compress' or 'decompress'
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
/// Compress the input string
Compress {
/// The string to compress
input: String,
},
/// Decompress the input string (must be compressed format)
Decompress {
/// The compressed string to decompress, in hex format
input: String,
},
}
fn compress_string(input: &str) -> Vec<u8> {
let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
encoder.write_all(input.as_bytes()).expect("Compression failed");
encoder.finish().expect("Failed to finish compression")
}
fn decompress_string(input: &[u8]) -> String {
let mut decoder = GzDecoder::new(Vec::new());
decoder.write_all(input).expect("Decompression failed");
let decompressed_data = decoder.finish().expect("Failed to finish decompression");
String::from_utf8(decompressed_data).expect("Invalid UTF-8")
}
fn main() {
let cli = Cli::parse();
match cli.command {
Commands::Compress { input } => {
let compressed = compress_string(&input);
// Convert to hex format for easier readability in the terminal
println!("Compressed (hex): {:?} / from {}b to {}b", hex::encode(compressed.clone()), input.len(), compressed.len());
}
Commands::Decompress { input } => {
// Decode hex string back to bytes
let compressed_bytes = hex::decode(input).expect("Invalid hex input");
let decompressed = decompress_string(&compressed_bytes);
println!("Decompressed: {}", decompressed);
}
}
}

41
gemma/src/bin/tct.rs Normal file
View File

@ -0,0 +1,41 @@
use clap::{ArgGroup, Parser};
use gemma::chip8::util::InstructionUtil;
/// TCT
/// Test Compression Tool
///
/// A Tool to take a plaintext JSON object of the memory state
/// for the CHIP-8 emulator and compress the input into compressed
/// text. Test sizes go from ~22k to ~1k which makes a difference over
/// hundreds of tests
/// Written by Trevor Merritt and optimized by ChatGPT
/// Simple file compression and decompression tool
#[derive(Parser, Debug)]
#[command(author, version, about)]
#[clap(group(
ArgGroup::new("mode")
.required(true)
.args(["compress", "decompress"]),
))]
struct Cli {
/// The filename to process
filename: String,
/// Compress the file
#[arg(short = 'c', long = "compress", group = "mode")]
compress: bool,
/// Decompress the file
#[arg(short = 'd', long = "decompress", group = "mode")]
decompress: bool,
}
fn main() {
let cli = Cli::parse();
if cli.compress {
InstructionUtil::compress_file(cli.filename).expect("Derp. Can't do that.");
} else if cli.decompress {
InstructionUtil::decompress_file(cli.filename).expect("Derp. Can't do that.");
}
}

View File

@ -1,3 +1,8 @@
use super::{
cpu_states::Chip8CpuStates, instructions::Chip8CpuInstructions,
system_memory::Chip8SystemMemory, video::Chip8Video,
};
use crate::chip8::cpu_states::Chip8CpuStates::WaitingForInstruction;
use crate::chip8::delay_timer::DelayTimer;
use crate::chip8::keypad::Keypad;
use crate::chip8::quirk_modes::QuirkMode;
@ -7,12 +12,7 @@ use crate::chip8::stack::Chip8Stack;
use log::debug;
use serde::{Deserialize, Serialize};
use super::{
cpu_states::Chip8CpuStates, instructions::Chip8CpuInstructions,
system_memory::Chip8SystemMemory, video::Chip8Video,
};
#[derive(Clone, Serialize, Deserialize, Debug)]
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
pub struct Chip8Computer {
pub num_cycles: i32,
pub memory: Chip8SystemMemory,
@ -52,6 +52,7 @@ impl Chip8Computer {
self.stack.reset();
self.memory.reset(quirk_mode);
self.quirk_mode = quirk_mode;
self.state = WaitingForInstruction;
}
pub fn dump_keypad_to_string(&self) -> String {
@ -117,8 +118,19 @@ impl Chip8Computer {
Chip8CpuStates::WaitingForKey => {
debug!("waiting for a key press...");
}
Chip8CpuStates::ExecutionComplete => {
debug!("Execution has completed.");
self.num_cycles += 1;
}
_ => {}
}
self
}
pub fn serialize(&self) -> String {
serde_json::to_string(&self).expect("Serialization failed")
}
pub fn deserialize(from: String) -> Chip8Computer {
serde_json::from_str(&from).expect("Failed to deserialize")
}
}

View File

@ -118,7 +118,7 @@ impl Chip8ComputerManager {
self.computer.load_bytes_to_memory(0x200, &bytes_to_load);
}
pub fn dump_to_string(&self, dump_type: ManagerDumpables) -> String {
pub fn dump_to_string(&self, dump_type: ManagerDumpables) -> String{
match dump_type {
ManagerDumpables::Video => self.computer.video_memory.format_as_string(),
ManagerDumpables::Registers => self.computer.registers.format_as_string(),

View File

@ -1,10 +1,11 @@
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Default, Serialize, Deserialize, Debug)]
#[derive(Clone, Copy, Default, Serialize, Deserialize, Debug, PartialEq)]
pub enum Chip8CpuStates {
#[default]
WaitingForInstruction,
WaitingForKey,
ExecutingInstruction,
Error,
ExecutionComplete,
}

View File

@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Serialize, Deserialize, Debug)]
#[derive(Clone, Copy, Serialize, Deserialize, Debug, PartialEq)]
pub struct DelayTimer {
counter: u8,
}

View File

@ -11,7 +11,7 @@ use std::ascii::AsciiExt;
use std::fmt::{Debug, Display, Formatter};
use std::ops::BitAnd;
use std::time::Instant;
use crate::chip8::cpu_states::Chip8CpuStates;
/*
nnn or addr - A 12-bit value, the lowest 12 bits of the instruction
n or nibble - A 4-bit value, the lowest 4 bits of the instruction
@ -326,9 +326,9 @@ impl Chip8CpuInstructions {
Chip8CpuInstructions::HIGH => INST_HIGH,
Chip8CpuInstructions::ORY(_, _) => INST_ORY,
JPX(_, _) => INST_JPX,
DW(_) => INST_DW,
XXXXERRORINSTRUCTION => "XX ERROR XX",
SCU(_) => INST_SCU,
DW(_) => INST_DW,
}
}
@ -681,7 +681,7 @@ impl Chip8CpuInstructions {
}
}
pub fn execute(&self, input: &mut Chip8Computer) -> Chip8Computer {
// print!("INSTRUCTION {}", self);
println!("INSTRUCTION {}", self);
let start_time = Instant::now();
let start_pc = input.registers.peek_pc();
input.registers.poke_pc(start_pc + 2);
@ -700,6 +700,12 @@ impl Chip8CpuInstructions {
}
// 0x1nnn Jump to Address
Chip8CpuInstructions::JPA(new_address) => {
// if the jump is to the same address as we are at, we are in an infinite
// loop and the program is effectively done. stop the CPU.
if *new_address == start_pc {
input.state = Chip8CpuStates::ExecutionComplete;
println!("Execution complete.");
}
input.registers.poke_pc(*new_address);
}
// 0x2nnn Call Subroutine

View File

@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
use crate::constants::CHIP8_KEYBOARD;
#[derive(Clone, Copy, Default, Serialize, Deserialize, Debug)]
#[derive(Clone, Copy, Default, Serialize, Deserialize, Debug, PartialEq)]
pub struct Keypad {
keys: [bool; 0x10],
}

View File

@ -1,7 +1,7 @@
use serde::{Deserialize, Serialize};
use std::fmt::Display;
#[derive(Default, Clone, Serialize, Deserialize, Copy, Debug)]
#[derive(Default, Clone, Serialize, Deserialize, Copy, Debug, PartialEq)]
pub enum QuirkMode {
Chip8,
XOChip,

View File

@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
/// Registers. numbered 1-16 publicly.
/// Privately using zero base array so -1 to shift from pub to priv.
#[derive(Clone, Copy, Serialize, Deserialize, Debug)]
#[derive(Clone, Copy, Serialize, Deserialize, Debug, PartialEq)]
pub struct Chip8Registers {
pub registers: [u8; 16],
pub i_register: u16,

View File

@ -1,7 +1,7 @@
use log::trace;
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Serialize, Deserialize, Debug)]
#[derive(Clone, Copy, Serialize, Deserialize, Debug, PartialEq)]
pub struct SoundTimer {
counter: i32,
}

View File

@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize};
#[derive(Clone, Default, Serialize, Deserialize, Debug)]
#[derive(Clone, Default, Serialize, Deserialize, Debug, PartialEq)]
pub struct Chip8Stack {
items: Vec<u16>,
}

View File

@ -7,7 +7,7 @@ use serde::Serialize;
use super::quirk_modes;
use super::quirk_modes::QuirkMode;
#[derive(Clone, Serialize, Deserialize, Debug)]
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
pub struct Chip8SystemMemory {
memory: Vec<u8>,
}

View File

@ -1,3 +1,11 @@
use flate2::read::ZlibDecoder;
use std::fs::File;
use std::io::{self, Read, Write};
use std::path::{Path, PathBuf};
use flate2::write::ZlibEncoder;
use flate2::Compression;
pub struct InstructionUtil {}
impl InstructionUtil {
@ -29,7 +37,6 @@ impl InstructionUtil {
}
pub fn join_bytes(high: u8, low: u8) -> u16 {
(high as u16) << 8 | low as u16
}
@ -40,7 +47,7 @@ impl InstructionUtil {
// n or nibble - A 4-bit value, the lowest 4 bits of the instruction
pub fn read_nibble_from_instruction(instruction_to_read_from: u16) -> u8 {
( instruction_to_read_from & 0x000F )as u8
(instruction_to_read_from & 0x000F) as u8
}
// x - A 4-bit value, the lower 4 bits of the high byte of the instruction
@ -61,4 +68,122 @@ impl InstructionUtil {
pub fn read_upper_byte_lower_nibble(to_read_from: u16) -> u8 {
((to_read_from & 0x0f00) >> 8) as u8
}
pub fn compress_file<P: AsRef<Path>>(to_compress: P) -> io::Result<()> {
let to_compress_path = to_compress.as_ref();
let target_filename = format!("{}.tct", to_compress_path.display());
println!(
"Compressing file: {} to {}",
to_compress_path.display(),
target_filename
);
// Open the source file
let mut source_file = File::open(&to_compress_path)?;
// Read the entire file into a buffer
let mut read_buffer = Vec::new();
source_file.read_to_end(&mut read_buffer)?;
// Initialize the encoder and compress the read data
let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
encoder.write_all(&read_buffer)?;
let encoded_data = encoder.finish()?;
println!(
"Compressed {} bytes into {} bytes.",
read_buffer.len(),
encoded_data.len()
);
// Write the encoded data to the target file
let mut target_file = File::create(target_filename)?;
target_file.write_all(&encoded_data)?;
Ok(())
}
pub fn decompress_file_to_string<P: AsRef<Path>>(to_decompress: P) -> io::Result<String> {
let to_decompress_path = to_decompress.as_ref();
// Ensure the file has the expected `.tct` extension
if to_decompress_path.extension() != Some("tct".as_ref()) {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"File must have a .tct extension",
));
}
println!(
"Decompressing file: {} to string",
to_decompress_path.display()
);
// Open the compressed file and set up the decoder
let compressed_file = File::open(&to_decompress_path)?;
let mut decoder = ZlibDecoder::new(compressed_file);
// Read decompressed data
let mut decompressed_data = Vec::new();
decoder.read_to_end(&mut decompressed_data)?;
// Convert decompressed byte data to a UTF-8 String
match String::from_utf8(decompressed_data) {
Ok(decompressed_string) => {
// Optionally, write decompressed data to a new file
println!(
"Decompressed {} bytes into {} bytes.",
to_decompress_path.metadata()?.len(),
decompressed_string.len()
);
Ok(decompressed_string)
}
Err(e) => {
// Handle invalid UTF-8 data (if the decompressed data isn't valid UTF-8)
Err(io::Error::new(io::ErrorKind::InvalidData, e))
}
}
}
pub fn decompress_file<P: AsRef<Path>>(to_decompress: P) -> io::Result<()> {
let to_decompress_path = to_decompress.as_ref();
// Ensure the file has the expected `.tct` extension
if to_decompress_path.extension() != Some("tct".as_ref()) {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"File must have a .tct extension",
));
}
// Derive the output filename by removing the `.tct` extension
let mut output_filename = PathBuf::from(to_decompress_path);
output_filename.set_extension("untct"); // Remove .tct extension to restore original file name
println!(
"Decompressing file: {} to {}",
to_decompress_path.display(),
output_filename.display()
);
// Open the compressed file and set up the decoder
let compressed_file = File::open(&to_decompress_path)?;
let mut decoder = ZlibDecoder::new(compressed_file);
// Read decompressed data
let mut decompressed_data = Vec::new();
decoder.read_to_end(&mut decompressed_data)?;
// Write decompressed data to the output file
let mut output_file = File::create(&output_filename)?;
output_file.write_all(&decompressed_data)?;
println!(
"Decompressed {} bytes into {} bytes.",
to_decompress_path.metadata()?.len(),
decompressed_data.len()
);
Ok(())
}
}

View File

@ -6,13 +6,13 @@ use crate::constants::{
use log::debug;
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Serialize, Deserialize, Debug)]
#[derive(Clone, Copy, Serialize, Deserialize, Debug, PartialEq)]
pub enum Chip8VideoModes {
LowRes,
HighRes,
}
#[derive(Clone, Serialize, Deserialize, Debug)]
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
pub struct Chip8Video {
memory: Vec<bool>,
pub has_frame_changed: bool,
@ -36,7 +36,8 @@ impl Chip8Video {
}
pub fn set_lowres(&mut self) {
self.current_res = LowRes
self.current_res = LowRes;
self.cls();
}
pub fn get_screen_resolution(&mut self) -> Chip8VideoModes {
@ -152,7 +153,6 @@ impl Chip8Video {
}
pub fn scroll_right(&mut self) {
println!("SCROLLRIGHTPRE:::[{}]", self.format_as_string());
let (width, height) = self.get_resolution();
for current_row in 0..height {
@ -168,11 +168,9 @@ impl Chip8Video {
// Clear the first 4 pixels in the current row
self.memory[row_offset..row_offset + 4].fill(false);
}
println!("SCROLLRIGHTPOST:::[{}]", self.format_as_string());
}
pub fn scroll_left(&mut self) {
println!("SCROLLLEFTPRE:::[{}]", self.format_as_string());
let (width, height) = self.get_resolution();
for current_row in 0..height {
@ -181,7 +179,6 @@ impl Chip8Video {
let target: usize = (row_offset + current_column) as usize;
let source: usize = target + 4;
self.memory[target] = self.memory[source];
println!("Moving from {source} to {target}");
}
let clear_start: usize = (row_offset + width - 4) as usize;
@ -189,7 +186,6 @@ impl Chip8Video {
self.memory[clear_start..clear_end].fill(false);
}
println!("SCROLLLEFTPOST:::[{}]", self.format_as_string());
}
pub fn scroll_up(&mut self, how_far: &u8) {
@ -240,7 +236,7 @@ impl Default for Chip8Video {
Chip8Video {
memory: mem,
has_frame_changed: false,
current_res: Chip8VideoModes::LowRes,
current_res: LowRes,
}
}
}

View File

@ -1,26 +1,24 @@
use std::fs::File;
use flate2::write::{GzDecoder, GzEncoder};
use flate2::Compression;
use gemma::chip8::computer::Chip8Computer;
use gemma::chip8::util::InstructionUtil;
use std::io;
use std::io::prelude::*;
fn load_result(to_load: &str) -> String {
let full_path = format!("resources/test/state/{}", to_load);
let full_path = format!("../resources/test/state/{}", to_load);
println!("Loading state => (([{}]))", full_path);
std::fs::read_to_string(full_path).unwrap()
}
fn load_compressed_result(file_path: &str) -> io::Result<String> {
// Load the compressed file contents
let compressed_data = fs::read(file_path)?;
// Create a GzDecoder to uncompress the data
let mut decoder = GzDecoder::new(&compressed_data[..]);
let mut decompressed_data = String::new();
// Read the decompressed data directly into a String
decoder.read_to_string(&mut decompressed_data)?;
Ok(decompressed_data)
let full_path = format!("../resources/test/state/{}", file_path);
println!(
"ATTEMPTING TO LOAD {} AS A COMPRESSED TEST RESULT.",
full_path
);
InstructionUtil::decompress_file_to_string(full_path)
}
fn load_rom(to_load: &str) -> Vec<u8> {
@ -33,21 +31,21 @@ fn test_serialization_round_trip() {
let expected_json = load_result("smoke_001_round_trip_serialize_deserialize.json");
// Serialize the Chip8Computer instance
let serialized = serde_json::to_string(&original_computer).expect("Serialization failed");
let serialized = Chip8Computer::serialize(&original_computer);
let deserialized = Chip8Computer::deserialize(expected_json.clone());
// Compare the serialized output to the expected JSON
println!("Serialized Output: [{}]", serialized);
assert_eq!(
serialized.trim(),
expected_json.trim(),
expected_json.clone().trim(),
"Serialized output does not match expected JSON"
);
// Deserialize back to Chip8Computer and assert equality
let deserialized_computer: Chip8Computer =
serde_json::from_str(&serialized).expect("Deserialization failed");
assert_eq!(
deserialized_computer, original_computer,
deserialized, original_computer,
"Deserialized instance does not match the original"
);
}
@ -55,9 +53,9 @@ fn test_serialization_round_trip() {
#[test]
fn computer_001_system_zero_state() {
let x = Chip8Computer::new();
let expected_string = load_compressed_result("smoke_002_round_trip_serialize_deserialize.tflt");
let serialized = serde_json::to_string(&x).unwrap();
let expected_string = load_compressed_result("computer_001_system_zero_state.tct")
.expect("Unable to read result");
let serialized = x.serialize();
assert_eq!(serialized, expected_string);
}
}

View File

@ -35,11 +35,13 @@ fn decoder_test_invalid_instructions() {
];
for i in invalid_to_encode {
assert_eq!(Chip8CpuInstructions::decode(i, &Chip8).encode(), 0xffff);
println!("TESTING 0x{i:04x}");
println!("DECOODED TO {:?}", Chip8CpuInstructions::decode(i, &Chip8));
assert!(matches!(
Chip8CpuInstructions::decode(i, &Chip8),
Chip8CpuInstructions::XXXXERRORINSTRUCTION
));
Chip8CpuInstructions::DW(i)
) ||
matches!(Chip8CpuInstructions::decode(i, &Chip8), Chip8CpuInstructions::XXXXERRORINSTRUCTION));
}
}

View File

@ -74,7 +74,6 @@ fn main() {
system.wait_for_instruction();
} else {
// do we need to release it?
if system.is_key_pressed(key_reg) {
system.release_key(key_reg);
}

View File

@ -12,6 +12,8 @@ use log::debug;
use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};
use imgui::sys::ImGuiIO;
use gemma::chip8::instructions::Chip8CpuInstructions;
const ROM_ROOT: &str = "resources/roms";
@ -43,70 +45,186 @@ impl GemmaImguiSupport {
pub fn video_display(system_to_control: &Chip8Computer, gui_state: &ImGuiUiState, ui: &Ui) {
// draw area size
let (width, height) = system_to_control.video_memory.get_resolution();
let draw_area_size = ui.io().display_size;
// println!("DRAW_AREA_SIZE = {}x{}", draw_area_size[0], draw_area_size[1]);
let cell_width = ((draw_area_size[0] as i32 / width) * 6) / 10;
let cell_height = ((draw_area_size[1] as i32 / height) * 6) / 10;
ui.window("Video")
.size([gui_state.video_window_size[0] as f32, gui_state.video_window_size[1] as f32], Condition::Once)
.build(|| {
let draw_area_size = ui.window_size();
let draw_offset = ui.window_pos();
// now lets move the draw_offset by 0,20 to get it off the window title bar
let draw_offset = [draw_offset[0], draw_offset[1] + 20.0];
// and reduce the draw area size by the same values. {}
let draw_area_size = [draw_area_size[0], draw_area_size[1] - 20.0];
// println!("DRAW_AREA_SIZE = {}x{}", draw_area_size[0], draw_area_size[1]);
let cell_width = ((draw_area_size[0] as i32 / width));
let cell_height = ((draw_area_size[1] as i32 / height));
let origin = ui.cursor_pos();
let fg = ui.get_foreground_draw_list();
if system_to_control.video_memory.is_highres() {
// ui.text("High Def Video here");
for current_row in 0..=height {
let y_offset = origin[1] as i32 + (current_row * cell_height);
for current_column in 0..=width {
let x_offset = origin[0] as i32 + (current_column * cell_width);
let current_origin = [x_offset as f32, y_offset as f32];
let current_limit = [
(x_offset + cell_width) as f32,
(y_offset + cell_height) as f32,
];
let memory_offset = (current_row * width + current_column) as u16;
let to_render = system_to_control.video_memory.peek(memory_offset);
let color: ImColor32 = if to_render {
gui_state.on_colour
} else {
gui_state.off_colour
};
fg.add_rect_filled_multicolor(
current_origin,
current_limit,
color,
color,
color,
color,
);
let origin = draw_offset;
let fg = ui.get_foreground_draw_list();
for current_row in 0..height {
let y_offset = origin[1] as i32 + (current_row * cell_height);
for current_column in 0..width {
let x_offset = origin[0] as i32 + (current_column * cell_width);
let current_origin = [x_offset as f32, y_offset as f32];
let current_limit = [
(x_offset + cell_width) as f32,
(y_offset + cell_height) as f32,
];
let memory_offset = (current_row * width + current_column) as u16;
let to_render = system_to_control.video_memory.peek(memory_offset);
let color: ImColor32 = if to_render {
gui_state.on_colour
} else {
gui_state.off_colour
};
fg.add_rect_filled_multicolor(
current_origin,
current_limit,
color,
color,
color,
color,
);
}
}
}).expect("cant draw the video i guess");
}
pub fn quirks_picker(system: &mut Chip8ComputerManager,
ui: &Ui) {
let selectors = [Chip8, SChipModern, XOChip];
for current_selector in selectors {
let mut working_selector =
ui.selectable_config(current_selector.clone().to_string());
match system.quirks_mode() {
Chip8 => {
working_selector = working_selector.selected(true);
}
SChipModern => {
working_selector = working_selector.selected(true);
}
XOChip => {
working_selector = working_selector.selected(true);
}
}
} else {
for current_row in 0..height {
let y_offset = origin[1] as i32 + (current_row * cell_height);
for current_column in 0..width {
let x_offset = origin[0] as i32 + (current_column * cell_width);
let current_origin = [x_offset as f32, y_offset as f32];
let current_limit = [
(x_offset + cell_width) as f32,
(y_offset + cell_height) as f32,
];
let memory_offset = (current_row * width + current_column) as u16;
let to_render = system_to_control.video_memory.peek(memory_offset);
let color: ImColor32 = if to_render {
gui_state.on_colour
} else {
gui_state.off_colour
};
fg.add_rect_filled_multicolor(
current_origin,
current_limit,
color,
color,
color,
color,
if working_selector.build() {
system.reset(current_selector);
println!("CLICK ON {}", &current_selector);
}
}
}
fn control_pickers(
system: &mut Chip8ComputerManager,
gui_state: &mut ImGuiUiState,
ui: &Ui,
) {
if CollapsingHeader::new("Controls").build(ui) {
// if the system has no program loaded hide the buttons.
let bytes: [u8; 2] = [
system.state().memory.peek(0x200),
system.state().memory.peek(0x201),
];
let show_buttons = true; // bytes[0] != 0 || bytes[1] == 0xe0;
if show_buttons {
if ui.button("Step") {
system.step();
};
ui.same_line();
if ui.button("Run") {
system.start();
}
}
ui.same_line();
if ui.button("Stop") {
system.stop();
}
ui.same_line();
if ui.button("Reset") {
system.reset(system.quirks_mode());
}
if ui.button("Dump Video Memory") {
println!("{}", system.dump_to_string(Video));
}
ui.same_line();
if ui.button("Dump Keypad State") {
println!("{}", system.dump_to_string(Keyboard));
}
ui.same_line();
if ui.button("Dump Registers") {
println!("{}", system.dump_to_string(Registers));
}
}
}
fn system_summary(
system: &Chip8ComputerManager,
ui: &Ui,
) {
/* System Step Counter */
ui.text(format!("Step {:04x}", system.num_cycles()).as_str());
ui.text(format!("Mode {}", system.quirks_mode()));
}
fn rom_lister(
system: &mut Chip8ComputerManager,
gui_state: &mut ImGuiUiState,
ui: &Ui,
) {
/* ROM Lister */
if CollapsingHeader::new("Roms").build(ui) {
let new_filename = GuiFileList::display_path(
gui_state.roms_root_path.clone(),
&gui_state.filename_to_load,
ui,
);
if !new_filename.is_empty() {
if new_filename != gui_state.filename_to_load {
debug!("NEW FILENAME SELECTED -> {new_filename}");
gui_state.filename_to_load = new_filename;
}
if ui.button("Load Program") {
let mut buffer: Vec<u8> = Vec::new();
let full_name = format!(
"{}/{}",
gui_state
.roms_root_path
.clone()
.as_os_str()
.to_string_lossy(),
gui_state.filename_to_load.to_string()
);
debug!("PREPARING TO LOAD {}", full_name);
let input_file = File::open(Path::new(&full_name));
input_file
.unwrap()
.read_to_end(&mut buffer)
.expect("Unable to read rom.");
system.load_new_program_to_system_memory(buffer);
}
}
}
}
fn display_options(
gui_state: &mut ImGuiUiState,
ui: &Ui,
) {
if CollapsingHeader::new("Options").build(ui) {
ui.checkbox("Show Memory", &mut gui_state.show_memory);
ui.same_line();
ui.checkbox("Show Video", &mut gui_state.show_video);
ui.same_line();
ui.checkbox("Show Registers", &mut gui_state.show_registers);
ui.same_line();
ui.checkbox("Show Keypad", &mut gui_state.show_keypad);
ui.input_int("Target IPS", &mut gui_state.target_ips)
.build();
};
}
pub fn system_controls(
system_to_control: &mut Chip8ComputerManager,
gui_state: &mut ImGuiUiState,
@ -116,114 +234,15 @@ impl GemmaImguiSupport {
ui.window("!!!! CONTROLS !!!!")
.position([100.0, 640.0], Condition::FirstUseEver)
.build(|| {
/* System Step Counter */
ui.text(format!("Step {:04x}", system_to_control.num_cycles()).as_str());
ui.text(format!("Mode {}", system_to_control.quirks_mode()));
/* ROM Lister */
if CollapsingHeader::new("Roms").build(ui) {
let new_filename = GuiFileList::display_path(
gui_state.roms_root_path.clone(),
&gui_state.filename_to_load,
ui,
);
if !new_filename.is_empty() {
if new_filename != gui_state.filename_to_load {
debug!("NEW FILENAME SELECTED -> {new_filename}");
gui_state.filename_to_load = new_filename;
}
if ui.button("Load Program") {
let mut buffer: Vec<u8> = Vec::new();
let full_name = format!(
"{}/{}",
gui_state
.roms_root_path
.clone()
.as_os_str()
.to_string_lossy(),
gui_state.filename_to_load.to_string()
);
debug!("PREPARING TO LOAD {}", full_name);
GemmaImguiSupport::system_summary(system_to_control, ui);
let input_file = File::open(Path::new(&full_name));
input_file
.unwrap()
.read_to_end(&mut buffer)
.expect("Unable to read rom.");
system_to_control.load_new_program_to_system_memory(buffer);
}
}
}
GemmaImguiSupport::rom_lister(system_to_control, gui_state, ui);
if CollapsingHeader::new("Controls").build(ui) {
// if the system has no program loaded hide the buttons.
let bytes: [u8; 2] = [
system_to_control.state().memory.peek(0x200),
system_to_control.state().memory.peek(0x201),
];
let show_buttons = bytes[0] != 0 || bytes[1] == 0xe0;
GemmaImguiSupport::control_pickers(system_to_control, gui_state, ui);
if show_buttons {
if ui.button("Step") {
system_to_control.step();
};
ui.same_line();
if ui.button("Run") {
system_to_control.start();
}
}
ui.same_line();
if ui.button("Stop") {
system_to_control.stop();
}
ui.same_line();
if ui.button("Reset") {
system_to_control.reset(system_to_control.quirks_mode());
}
if ui.button("Dump Video Memory") {
println!("{}", system_to_control.dump_to_string(Video));
}
ui.same_line();
if ui.button("Dump Keypad State") {
debug!("{}", system_to_control.dump_to_string(Keyboard));
}
ui.same_line();
if ui.button("Dump Registers") {
debug!("{}", system_to_control.dump_to_string(Registers));
}
}
GemmaImguiSupport::display_options(gui_state, ui);
if CollapsingHeader::new("Options").build(ui) {
ui.checkbox("Show Memory", &mut gui_state.show_memory);
ui.same_line();
ui.checkbox("Show Video", &mut gui_state.show_video);
ui.same_line();
ui.checkbox("Show Registers", &mut gui_state.show_registers);
ui.same_line();
ui.checkbox("Show Keypad", &mut gui_state.show_keypad);
ui.input_int("Target IPS", &mut gui_state.target_ips)
.build();
};
let selectors = [Chip8, SChipModern, XOChip];
for current_selector in selectors {
let mut working_selector =
ui.selectable_config(current_selector.clone().to_string());
match system_to_control.quirks_mode() {
Chip8 => {
working_selector = working_selector.selected(true);
}
SChipModern => {
working_selector = working_selector.selected(true);
}
XOChip => {
working_selector = working_selector.selected(true);
}
}
if working_selector.build() {
system_to_control.reset(current_selector);
println!("CLICK ON {}", &current_selector);
}
}
GemmaImguiSupport::quirks_picker(system_to_control, ui);
});
}
@ -232,7 +251,7 @@ impl GemmaImguiSupport {
.position([100.0, 640.0], Condition::FirstUseEver)
.build(|| {
ui.text("Registers");
for i in 1..0x10 {
for i in 0..0x10 {
ui.text(format!("V{:X}: {}", i, system.registers.peek(i)));
if i != 7 {
ui.same_line();

View File

@ -1,7 +1,7 @@
use std::ffi::OsString;
use std::fs::read_dir;
use std::path::PathBuf;
use imgui::Ui;
use imgui::{ChildWindow, Ui};
use log::debug;
pub struct GuiFileList {}
@ -20,16 +20,22 @@ impl GuiFileList {
known_files.sort();
for (index, entry) in known_files.iter().enumerate() {
let mut working_select = ui.selectable_config(entry.clone().into_string().unwrap().to_string());
if *entry.to_str().unwrap() == *selected_filename.as_str() {
working_select = working_select.selected(true);
}
if working_select.build() {
debug!("SELECTED {index} / {entry:?}");
working_filename = entry.clone().into_string().unwrap();
};
}
ui.child_window("Item List")
.size([0.0, 200.0])
.border(false)
.build(|| {
for (index, entry) in known_files.iter().enumerate() {
let mut working_select = ui.selectable_config(entry.clone().into_string().unwrap().to_string());
if *entry.to_str().unwrap() == *selected_filename.as_str() {
working_select = working_select.selected(true);
}
if working_select.build() {
debug!("SELECTED {index} / {entry:?}");
working_filename = entry.clone().into_string().unwrap();
};
}
});
working_filename
}
}

View File

@ -179,7 +179,7 @@ pub fn create_context() -> imgui::Context {
}),
},
]);
imgui.set_ini_filename(None);
// imgui.set_ini_filename(None);
imgui
}

View File

@ -16,7 +16,9 @@ pub struct ImGuiUiState {
pub frame_time: u32,
pub last_frame_instant: Instant,
pub target_ips: i32,
pub roms_root_path: PathBuf
pub roms_root_path: PathBuf,
pub video_window_size: [i32; 2],
pub control_window_size: [i32; 2]
}
impl Clone for ImGuiUiState {
@ -33,7 +35,9 @@ impl Clone for ImGuiUiState {
frame_time: self.frame_time,
last_frame_instant: self.last_frame_instant,
target_ips: self.target_ips,
roms_root_path: self.roms_root_path.to_owned()
roms_root_path: self.roms_root_path.to_owned(),
video_window_size: self.video_window_size,
control_window_size: self.control_window_size
}
}
}
@ -51,8 +55,10 @@ impl Default for ImGuiUiState {
is_running: false,
frame_time: 16,
last_frame_instant: Instant::now(),
target_ips: 200000,
roms_root_path: PathBuf::from(ROMPATH_DEFAULT)
target_ips: 20,
roms_root_path: PathBuf::from(ROMPATH_DEFAULT),
control_window_size: [200, 600],
video_window_size: [800, 600]
}
}
}

View File

@ -1,10 +0,0 @@
[package]
name = "gemmautil"
version = "0.1.0"
edition = "2021"
[dependencies]
gemma = { path = "../gemma" }
clap.workspace = true
pest = "2.7.14"
pest_derive = "2.7.14"

Binary file not shown.

BIN
resources/roms/gradsim.ch8 Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
1f8b08000000000000ffed9bcb8adb301440f7fd162dfc0c136d0b854257dd743104636c4d2212dba924b704e37fafecb13b4e42da4099a10c87631d45575792e56897b8abdb2a2b4ec5415919884a558d39c96eaa1fa324106192fc2e433b8ec47a35d85f61188db170f55c450f63bd0c4dd562021fbac89f9a67a12979eaf66bad92e15af69de5bc4c7e159ad78eaeeb79c9b98c63a3e47cd38bbcab5d2e624309e0d509c7271dff212316e1da1fcc5be387ef331d4a3a9670bdfee7ccf98c44893f0f69348eba9139cd3597f8e6dac1f9da7178e78cc3bddcbb9ff40d3397596f3fe7bdcffdde3c0000000000000000000000000078376c7a61d4565ba78c95ddcbc7c7bf8d133a9bb365208e854cc3a817b669eb3273baf2d1aef08db1bb17a53ae4a7cbb8ff59b2173f74a99aecf20f1b4ff9c12a8131c618638c31c618638c31c618638c31c618638c31c618638c31c618e3ffd81bb1cb6df664f24a65c52eafb7aa94cfdd456b8caa5d6694955f9a9f5f95ed8575b953f25bae9daeb79f1af3b9b6ceb485d34d2df6ea74cc4bd9f9dabefa6b059bf15e8abdecb453955fcf07beb7daecb3aa2995fcb8d3c787fec32fcefd4d09ab520000