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:
@@ -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"}
|
||||
@@ -0,0 +1,79 @@
|
||||
use clap::{Arg, Command};
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::io::{BufReader, Read};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct CliArgs {
|
||||
input: String,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("Taxation is Theft!");
|
||||
// Set up the command line arguments
|
||||
let matches = Command::new("my_program")
|
||||
.about("Processes an input file and outputs it with a specified bit width")
|
||||
.arg(
|
||||
Arg::new("input")
|
||||
.help("The input file to process")
|
||||
.required(true)
|
||||
.index(1),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
// Parse the command-line arguments
|
||||
let args = CliArgs {
|
||||
input: matches.get_one::<String>("input").unwrap().to_string(),
|
||||
};
|
||||
|
||||
// Use the parsed arguments
|
||||
println!("Input file: {}", args.input);
|
||||
|
||||
// behave like a shift register and load each character from the file 1 by 1.
|
||||
let results = read_file_to_bools(&args.input);
|
||||
for result in results.unwrap().bytes() {
|
||||
print!("0x{:02x}, ", result.unwrap());
|
||||
}
|
||||
}
|
||||
fn read_file_to_bools(file_path: &str) -> io::Result<Vec<u8>> {
|
||||
// Open the file
|
||||
let file = File::open(file_path)?;
|
||||
let mut reader = BufReader::new(file);
|
||||
|
||||
let mut bytes = Vec::new();
|
||||
reader.read_to_end(&mut bytes)?;
|
||||
|
||||
let mut output = Vec::new();
|
||||
let mut current_byte = 0u8;
|
||||
let mut bit_index = 0;
|
||||
|
||||
for &byte in &bytes {
|
||||
// Convert ASCII character '1' or '0' to boolean, skip any other characters
|
||||
let bit = match byte {
|
||||
b'1' => true,
|
||||
b'0' => false,
|
||||
_ => continue, // Skip non-'1' or '0' characters
|
||||
};
|
||||
|
||||
// Set the appropriate bit in the current byte
|
||||
if bit {
|
||||
current_byte |= 1 << (7 - bit_index); // Set the bit at the correct position
|
||||
}
|
||||
|
||||
bit_index += 1;
|
||||
|
||||
// Once we have filled 8 bits, push the byte and reset
|
||||
if bit_index == 8 {
|
||||
output.push(current_byte);
|
||||
current_byte = 0;
|
||||
bit_index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// If there are remaining bits, push the last byte (it will be partially filled)
|
||||
if bit_index > 0 {
|
||||
output.push(current_byte);
|
||||
}
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
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.
|
||||
// no labels.
|
||||
// nothing fun.
|
||||
// VALID FORMAT
|
||||
// <INSTRUCTION> <0x|0X><parameter>[, ][<0x|OX><parameter>[, ][<0x|OX><parameter>]][; comment]
|
||||
use pest::Parser;
|
||||
use pest_derive::Parser;
|
||||
|
||||
#[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");
|
||||
|
||||
let file = Chip8AsmParser::parse(Rule::file, &unparsed)
|
||||
.expect("Unable to parse. Try again.")
|
||||
.next()
|
||||
.unwrap();
|
||||
|
||||
println!("PREPARING TO WRITE THE TARGET FILE...");
|
||||
let mut target_file = File::create("thisismytarget.ch8").expect("Unable to create target");
|
||||
|
||||
for record in file.into_inner() {
|
||||
// let mut working_instruction = Chip8CpuInstructions::XXXXERRORINSTRUCTION;
|
||||
match record.as_rule() {
|
||||
Rule::record => {
|
||||
print!("record = {:?}\t", record.as_str());
|
||||
let x = Chip8CpuInstructions::from_str(record.as_str());
|
||||
println!("DECODED TO {:?} {:04x}", x, x.encode());
|
||||
let (high, low) = InstructionUtil::split_bytes(x.encode());
|
||||
target_file
|
||||
.write_all(&[high, low])
|
||||
.expect("Unable to write to the file.");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
use std::path::Path;
|
||||
use clap::{command, Parser};
|
||||
use gemma::chip8::instructions::Chip8CpuInstructions;
|
||||
use gemma::chip8::instructions::Chip8CpuInstructions::XXXXERRORINSTRUCTION;
|
||||
use gemma::chip8::quirk_modes::QuirkMode::Chip8;
|
||||
|
||||
/// ch8disasm
|
||||
///
|
||||
/// Provide disassembled version of the CH8 file provided.
|
||||
#[derive(Parser)]
|
||||
#[command(version, about, long_about = None)]
|
||||
struct DisassemblerApp {
|
||||
#[arg(short)]
|
||||
input_file: Box<Path>,
|
||||
#[arg(short)]
|
||||
output_file: Option<Box<Path>>,
|
||||
}
|
||||
|
||||
struct Disassembler {}
|
||||
impl Disassembler {
|
||||
pub fn disassemble(from_data: Vec<u8>) -> String {
|
||||
let mut working_instruction: u16 = 0x0000;
|
||||
|
||||
// read the input data and loop through it byte by byte.
|
||||
let mut output_string = String::new();
|
||||
|
||||
for (offset, byte) in from_data.iter().enumerate() {
|
||||
working_instruction = (working_instruction << 8) | (*byte as u16);
|
||||
if offset % 2 != 0 {
|
||||
let decoded = Chip8CpuInstructions::decode(working_instruction, &Chip8);
|
||||
let decoded_string = decoded.to_string();
|
||||
let mut current_parts = String::new();
|
||||
|
||||
match decoded {
|
||||
XXXXERRORINSTRUCTION => {
|
||||
current_parts = format!("DW 0x{:04x}", working_instruction);
|
||||
}
|
||||
_ => {
|
||||
current_parts = format!("{}", decoded);
|
||||
}
|
||||
};
|
||||
|
||||
let target_length: i32 = 25;
|
||||
let spacing_length = target_length.saturating_sub(current_parts.len() as i32);
|
||||
|
||||
// now add the rest after the string
|
||||
let x = spacing_length as usize;
|
||||
current_parts = format!("{}{:<x$}; Bytes [0x{:04x}] offset [0x{:04x}]\n", current_parts, " ", working_instruction, offset -1);
|
||||
// println!("SHOULD OUTPUT: [{current_parts}]");
|
||||
output_string = output_string.to_string() + &*current_parts.to_string();
|
||||
|
||||
working_instruction = 0x0000;
|
||||
}
|
||||
}
|
||||
|
||||
output_string
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("Taxation is Theft");
|
||||
let result = DisassemblerApp::parse();
|
||||
println!("PREPARING TO DISASSEMBLE {:?}", result.input_file.file_name());
|
||||
|
||||
|
||||
let source_file = result.input_file.to_str().unwrap();
|
||||
let decompiled_program = Disassembler::disassemble(
|
||||
std::fs::read(source_file).unwrap()
|
||||
);
|
||||
|
||||
|
||||
match result.output_file {
|
||||
None => {
|
||||
println!("Output to console.");
|
||||
println!("OS: \n\n{}", decompiled_program);
|
||||
}
|
||||
Some(target) => {
|
||||
println!("Output to {:?}", target);
|
||||
std::fs::write(target, decompiled_program).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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],
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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>,
|
||||
}
|
||||
|
||||
@@ -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>,
|
||||
}
|
||||
|
||||
+127
-2
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
WHITESPACE = _{ " " | "\t" } // Allow tabs and multiple spaces
|
||||
instruction = { ASCII_ALPHA+ } // Instruction, e.g., "CLS" or "LDX"
|
||||
parameter = { ("0x" | "0X") ~ ASCII_HEX_DIGIT+ } // Parameter format, e.g., "0X250"
|
||||
comment = { ";" ~ (!"\n" ~ ANY)* } // Capture everything after ";", up to end of line
|
||||
parameters = { (parameter ~ (WHITESPACE* ~ ","* ~ WHITESPACE* ~ parameter)*)?} // Zero or more parameters
|
||||
record = { instruction ~ WHITESPACE* ~ parameters ~ WHITESPACE* ~ comment? } // Record structure
|
||||
file = { SOI ~ (record ~ (("\r\n" | "\n") | WHITESPACE*))* ~ EOI } // Allow for trailing newlines and empty lines
|
||||
+18
-20
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user