prep to add savestates
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
use gemma::chip8::computer::Chip8Computer;
|
||||
use gemma::chip8::computer_manager::Chip8ComputerManager;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::ops::Add;
|
||||
use std::path::Path;
|
||||
use std::time::{Duration, Instant};
|
||||
use gemma::chip8::computer::Chip8Computer;
|
||||
use gemma::chip8::computer_manager::Chip8ComputerManager;
|
||||
|
||||
const ROM_ROOT: &str = "resources/roms";
|
||||
|
||||
@@ -12,8 +12,11 @@ fn main() {
|
||||
let mut x = Chip8ComputerManager::new();
|
||||
|
||||
let mut buffer = Vec::new();
|
||||
let mut input_file = File::open(Path::new(&(ROM_ROOT.to_string() + "/mandelbrot_bench.ch8"))).expect("derp");
|
||||
input_file.read_to_end(&mut buffer).expect("unable to read file");
|
||||
let mut input_file =
|
||||
File::open(Path::new(&(ROM_ROOT.to_string() + "/mandelbrot_bench.ch8"))).expect("derp");
|
||||
input_file
|
||||
.read_to_end(&mut buffer)
|
||||
.expect("unable to read file");
|
||||
x.load_new_program_to_system_memory((&*buffer).into());
|
||||
|
||||
for _ in 0..10 {
|
||||
@@ -26,17 +29,17 @@ fn main() {
|
||||
x.step();
|
||||
}
|
||||
|
||||
let num = num_cycles.to_string()
|
||||
let num = num_cycles
|
||||
.to_string()
|
||||
.as_bytes()
|
||||
.rchunks(3)
|
||||
.rev()
|
||||
.map(std::str::from_utf8)
|
||||
.collect::<Result<Vec<&str>, _>>()
|
||||
.unwrap()
|
||||
.join(","); // separator
|
||||
|
||||
.join(","); // separator
|
||||
|
||||
let num_ips = num_cycles / 1000000;
|
||||
println!("Completed at {num_ips} Mips.");
|
||||
println!("Completed at {num_ips} Mips. ({num})");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+304
-386
@@ -1,14 +1,14 @@
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
use std::ops::BitAnd;
|
||||
use std::time::Instant;
|
||||
use log::debug;
|
||||
use rand::Rng;
|
||||
use crate::chip8::computer::{Chip8Computer};
|
||||
use crate::chip8::computer::Chip8Computer;
|
||||
use crate::chip8::cpu_states::Chip8CpuStates::WaitingForKey;
|
||||
use crate::chip8::instructions::Chip8CpuInstructions::*;
|
||||
use crate::chip8::quirk_modes::QuirkMode;
|
||||
use crate::chip8::util::InstructionUtil;
|
||||
use crate::constants::{*};
|
||||
use crate::constants::*;
|
||||
use log::debug;
|
||||
use rand::Rng;
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
use std::ops::BitAnd;
|
||||
use std::time::Instant;
|
||||
|
||||
/*
|
||||
nnn or addr - A 12-bit value, the lowest 12 bits of the instruction
|
||||
@@ -69,7 +69,7 @@ pub enum Chip8CpuInstructions {
|
||||
ADD(u8, u8),
|
||||
/// 8xy0
|
||||
/// Adds the value kk to the value of register Vx, then stores the result in Vx.
|
||||
LDR_Y(u8, u8),
|
||||
LDRY(u8, u8),
|
||||
/// 8xy1
|
||||
/// Stores the value of register Vy in register Vx.
|
||||
OR(u8, u8),
|
||||
@@ -184,7 +184,7 @@ pub enum Chip8CpuInstructions {
|
||||
/// Set delay timer = Vx.
|
||||
///
|
||||
/// DT is set equal to the value of Vx.
|
||||
LDD(u8), // 0xFx15 Set Delay Timer
|
||||
LDD(u8), // 0xFx15 Set Delay Timer
|
||||
/// Fx18
|
||||
/// Set sound timer = Vx.
|
||||
///
|
||||
@@ -268,7 +268,7 @@ pub enum Chip8CpuInstructions {
|
||||
///
|
||||
/// scroll screen content down N pixel, in XO-CHIP only selected bit
|
||||
/// planes are scrolled (Quirks are HP48 specific)
|
||||
SCU(u8)
|
||||
SCU(u8),
|
||||
}
|
||||
|
||||
impl Chip8CpuInstructions {
|
||||
@@ -296,7 +296,7 @@ impl Chip8CpuInstructions {
|
||||
Chip8CpuInstructions::LDRD(_) => INST_LDRD,
|
||||
Chip8CpuInstructions::LDRI(_) => INST_LDRI,
|
||||
Chip8CpuInstructions::LDRK(_) => INST_LDRK,
|
||||
Chip8CpuInstructions::LDR_Y(_, _) => INST_LDRY,
|
||||
Chip8CpuInstructions::LDRY(_, _) => INST_LDRY,
|
||||
Chip8CpuInstructions::OR(_, _) => INST_OR,
|
||||
Chip8CpuInstructions::RET => INST_RET,
|
||||
Chip8CpuInstructions::RND(_, _) => INST_RND,
|
||||
@@ -320,42 +320,42 @@ impl Chip8CpuInstructions {
|
||||
Chip8CpuInstructions::ORY(_, _) => INST_ORY,
|
||||
JPX(_, _) => INST_JPX,
|
||||
XXXXERRORINSTRUCTION => "XX ERROR XX",
|
||||
SCU(_) => INST_SCU
|
||||
SCU(_) => INST_SCU,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn operands(&self) -> String {
|
||||
match self {
|
||||
JPX(x, addr) => {
|
||||
let addr_for_display = (*x as u16 ) << 8 | *addr;
|
||||
JPX(x, addr) => {
|
||||
let addr_for_display = (*x as u16) << 8 | *addr;
|
||||
format!("0x{x:02x}, 0x{addr_for_display:04x}")
|
||||
}
|
||||
Chip8CpuInstructions::SYS(addr) |
|
||||
Chip8CpuInstructions::JPI(addr) |
|
||||
Chip8CpuInstructions::JPA(addr) |
|
||||
Chip8CpuInstructions::LDIA(addr) |
|
||||
Chip8CpuInstructions::CALL(addr) => {
|
||||
Chip8CpuInstructions::SYS(addr)
|
||||
| Chip8CpuInstructions::JPI(addr)
|
||||
| Chip8CpuInstructions::JPA(addr)
|
||||
| Chip8CpuInstructions::LDIA(addr)
|
||||
| Chip8CpuInstructions::CALL(addr) => {
|
||||
format!("0x{addr:04x}")
|
||||
}
|
||||
Chip8CpuInstructions::SEX(x, byte) |
|
||||
Chip8CpuInstructions::SNEB(x, byte) |
|
||||
Chip8CpuInstructions::LDR(x, byte) |
|
||||
Chip8CpuInstructions::RND(x, byte) |
|
||||
Chip8CpuInstructions::ADD(x, byte) => {
|
||||
Chip8CpuInstructions::SEX(x, byte)
|
||||
| Chip8CpuInstructions::SNEB(x, byte)
|
||||
| Chip8CpuInstructions::LDR(x, byte)
|
||||
| Chip8CpuInstructions::RND(x, byte)
|
||||
| Chip8CpuInstructions::ADD(x, byte) => {
|
||||
format!("0x{x:02x}, 0x{byte:02x}")
|
||||
}
|
||||
// Reg, Reg
|
||||
SEY(x, y) |
|
||||
LDR_Y(x, y) |
|
||||
Chip8CpuInstructions::OR(x, y) |
|
||||
Chip8CpuInstructions::AND(x, y) |
|
||||
Chip8CpuInstructions::ORY(x, y) |
|
||||
Chip8CpuInstructions::ADDR(x, y) |
|
||||
Chip8CpuInstructions::SUB(x, y) |
|
||||
Chip8CpuInstructions::SHR(x, y) |
|
||||
Chip8CpuInstructions::SUBC(x, y) |
|
||||
Chip8CpuInstructions::SHL(x, y) |
|
||||
Chip8CpuInstructions::SNEY(x, y) => {
|
||||
SEY(x, y)
|
||||
| Chip8CpuInstructions::LDRY(x, y)
|
||||
| Chip8CpuInstructions::OR(x, y)
|
||||
| Chip8CpuInstructions::AND(x, y)
|
||||
| Chip8CpuInstructions::ORY(x, y)
|
||||
| Chip8CpuInstructions::ADDR(x, y)
|
||||
| Chip8CpuInstructions::SUB(x, y)
|
||||
| Chip8CpuInstructions::SHR(x, y)
|
||||
| Chip8CpuInstructions::SUBC(x, y)
|
||||
| Chip8CpuInstructions::SHL(x, y)
|
||||
| Chip8CpuInstructions::SNEY(x, y) => {
|
||||
format!("0x{x:01x}, 0x{y:01x}")
|
||||
}
|
||||
// Reg, Reg, Nibble
|
||||
@@ -363,34 +363,32 @@ impl Chip8CpuInstructions {
|
||||
format!("0x{x:02x}, 0x{y:02x}, 0x{nibble:02x}")
|
||||
}
|
||||
// Registers. 0-F
|
||||
Chip8CpuInstructions::SCU(x) |
|
||||
Chip8CpuInstructions::LDD(x) |
|
||||
Chip8CpuInstructions::LDIS(x) |
|
||||
Chip8CpuInstructions::ADDI(x) |
|
||||
Chip8CpuInstructions::LDFX(x) |
|
||||
Chip8CpuInstructions::BCD(x) |
|
||||
Chip8CpuInstructions::LDIX(x) |
|
||||
Chip8CpuInstructions::LDRD(x) |
|
||||
Chip8CpuInstructions::LDRK(x) |
|
||||
Chip8CpuInstructions::LDRI(x) |
|
||||
Chip8CpuInstructions::LDF2(x) |
|
||||
Chip8CpuInstructions::STR(x) |
|
||||
Chip8CpuInstructions::LIDR(x) |
|
||||
Chip8CpuInstructions::SCD(x) |
|
||||
Chip8CpuInstructions::SKNP(x) |
|
||||
Chip8CpuInstructions::SKP(x) => {
|
||||
Chip8CpuInstructions::SCU(x)
|
||||
| Chip8CpuInstructions::LDD(x)
|
||||
| Chip8CpuInstructions::LDIS(x)
|
||||
| Chip8CpuInstructions::ADDI(x)
|
||||
| Chip8CpuInstructions::LDFX(x)
|
||||
| Chip8CpuInstructions::BCD(x)
|
||||
| Chip8CpuInstructions::LDIX(x)
|
||||
| Chip8CpuInstructions::LDRD(x)
|
||||
| Chip8CpuInstructions::LDRK(x)
|
||||
| Chip8CpuInstructions::LDRI(x)
|
||||
| Chip8CpuInstructions::LDF2(x)
|
||||
| Chip8CpuInstructions::STR(x)
|
||||
| Chip8CpuInstructions::LIDR(x)
|
||||
| Chip8CpuInstructions::SCD(x)
|
||||
| Chip8CpuInstructions::SKNP(x)
|
||||
| Chip8CpuInstructions::SKP(x) => {
|
||||
format!("0x{x:02x}")
|
||||
}
|
||||
Chip8CpuInstructions::EXIT |
|
||||
Chip8CpuInstructions::HIGH |
|
||||
Chip8CpuInstructions::LOW |
|
||||
Chip8CpuInstructions::SCL |
|
||||
Chip8CpuInstructions::XXXXERRORINSTRUCTION |
|
||||
Chip8CpuInstructions::SCR |
|
||||
Chip8CpuInstructions::CLS |
|
||||
Chip8CpuInstructions::RET => {
|
||||
String::new()
|
||||
}
|
||||
Chip8CpuInstructions::EXIT
|
||||
| Chip8CpuInstructions::HIGH
|
||||
| Chip8CpuInstructions::LOW
|
||||
| Chip8CpuInstructions::SCL
|
||||
| Chip8CpuInstructions::XXXXERRORINSTRUCTION
|
||||
| Chip8CpuInstructions::SCR
|
||||
| Chip8CpuInstructions::CLS
|
||||
| Chip8CpuInstructions::RET => String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -406,154 +404,85 @@ impl Display for Chip8CpuInstructions {
|
||||
impl Chip8CpuInstructions {
|
||||
pub fn from_str(input: &str) -> Chip8CpuInstructions {
|
||||
let mut parts = input.split(" ");
|
||||
// print!("THERE ARE {} PARTS", parts.clone().count());
|
||||
// print!("THERE ARE {} PARTS", parts.clone().count());
|
||||
let first_part = parts.next().unwrap_or("");
|
||||
// take the next value...
|
||||
// ...strip off the extra...
|
||||
// ...convert it to an integer from base 16
|
||||
let param1 = u16::from_str_radix(parts.next().unwrap_or("0").trim_start_matches("0x").trim_end_matches(","), 16).unwrap_or(0);
|
||||
let param2 = u16::from_str_radix(parts.next().unwrap_or("0").trim_start_matches("0x").trim_end_matches(","), 16).unwrap_or(0);
|
||||
let param3 = u16::from_str_radix(parts.next().unwrap_or("0").trim_start_matches("0x").trim_end_matches(","), 16).unwrap_or(0);
|
||||
// println!("\tFirst part is {:?} / {:?} / {:?} / {:?}", first_part, param1 ,param2 ,param3);
|
||||
let param1 = u16::from_str_radix(
|
||||
parts
|
||||
.next()
|
||||
.unwrap_or("0")
|
||||
.trim_start_matches("0x")
|
||||
.trim_end_matches(","),
|
||||
16,
|
||||
)
|
||||
.unwrap_or(0);
|
||||
let param2 = u16::from_str_radix(
|
||||
parts
|
||||
.next()
|
||||
.unwrap_or("0")
|
||||
.trim_start_matches("0x")
|
||||
.trim_end_matches(","),
|
||||
16,
|
||||
)
|
||||
.unwrap_or(0);
|
||||
let param3 = u16::from_str_radix(
|
||||
parts
|
||||
.next()
|
||||
.unwrap_or("0")
|
||||
.trim_start_matches("0x")
|
||||
.trim_end_matches(","),
|
||||
16,
|
||||
)
|
||||
.unwrap_or(0);
|
||||
// println!("\tFirst part is {:?} / {:?} / {:?} / {:?}", first_part, param1 ,param2 ,param3);
|
||||
match first_part {
|
||||
INST_ADDI => {
|
||||
ADDI(param1 as u8)
|
||||
}
|
||||
INST_ADD => {
|
||||
ADD(param1 as u8, param2 as u8)
|
||||
}
|
||||
INST_CLS => {
|
||||
CLS
|
||||
}
|
||||
INST_DRW => {
|
||||
DRW(param1 as u8, param2 as u8, param3 as u8)
|
||||
}
|
||||
INST_CALL => {
|
||||
CALL(param1)
|
||||
}
|
||||
INST_SYS => {
|
||||
SYS(param1)
|
||||
}
|
||||
INST_RET => {
|
||||
RET
|
||||
}
|
||||
INST_JPA => {
|
||||
JPA(param1)
|
||||
}
|
||||
INST_JPI => {
|
||||
JPI(param1)
|
||||
}
|
||||
INST_SEX => {
|
||||
SEX(param1 as u8, param2 as u8)
|
||||
}
|
||||
INST_SNEB => {
|
||||
SNEB(param1 as u8, param2 as u8)
|
||||
}
|
||||
INST_SCD => {
|
||||
SCD(param1 as u8)
|
||||
}
|
||||
INST_STR => {
|
||||
STR(param1 as u8)
|
||||
}
|
||||
INST_SCL => {
|
||||
SCL
|
||||
}
|
||||
INST_EXIT => {
|
||||
EXIT
|
||||
}
|
||||
INST_LOW => {
|
||||
LOW
|
||||
}
|
||||
INST_HIGH => {
|
||||
HIGH
|
||||
}
|
||||
INST_SEY => {
|
||||
SEY(param1 as u8, param2 as u8)
|
||||
}
|
||||
INST_LDRY => {
|
||||
LDR_Y(param1 as u8, param2 as u8)
|
||||
}
|
||||
INST_LDR => {
|
||||
LDR(param1 as u8, param2 as u8)
|
||||
}
|
||||
INST_OR => {
|
||||
OR(param1 as u8, param2 as u8)
|
||||
}
|
||||
INST_AND => {
|
||||
AND(param1 as u8, param2 as u8)
|
||||
}
|
||||
INST_ORY => {
|
||||
ORY(param1 as u8, param2 as u8)
|
||||
}
|
||||
INST_ADDR => {
|
||||
ADDR(param1 as u8, param2 as u8)
|
||||
}
|
||||
INST_SUB => {
|
||||
SUB(param1 as u8, param2 as u8)
|
||||
}
|
||||
INST_SHR => {
|
||||
SHR(param1 as u8, param2 as u8)
|
||||
}
|
||||
INST_SHL => {
|
||||
SHL(param1 as u8, param2 as u8)
|
||||
}
|
||||
INST_SUBC => {
|
||||
SUBC(param1 as u8, param2 as u8)
|
||||
}
|
||||
INST_SNEY => {
|
||||
SNEY(param1 as u8, param2 as u8)
|
||||
}
|
||||
INST_LDIA => {
|
||||
LDIA(param1)
|
||||
}
|
||||
INST_RND => {
|
||||
RND(param1 as u8, param2 as u8)
|
||||
}
|
||||
INST_SKP => {
|
||||
SKP(param1 as u8)
|
||||
}
|
||||
INST_SKNP => {
|
||||
SKNP(param1 as u8)
|
||||
}
|
||||
INST_LDRD => {
|
||||
LDRD(param1 as u8)
|
||||
}
|
||||
INST_LDRK => {
|
||||
LDRK(param1 as u8)
|
||||
}
|
||||
INST_LDRI => {
|
||||
LDRI(param1 as u8)
|
||||
}
|
||||
INST_BCD => {
|
||||
BCD(param1 as u8)
|
||||
}
|
||||
INST_LDF => {
|
||||
LDFX(param1 as u8)
|
||||
}
|
||||
INST_LDF2 => {
|
||||
LDF2(param1 as u8)
|
||||
}
|
||||
INST_LDIX => {
|
||||
LDIX(param1 as u8)
|
||||
}
|
||||
INST_LIDR => {
|
||||
LIDR(param1 as u8)
|
||||
}
|
||||
INST_LDIS => {
|
||||
LDIS(param1 as u8)
|
||||
}
|
||||
INST_STR => {
|
||||
STR(param1 as u8)
|
||||
}
|
||||
INST_LDD => {
|
||||
LDD(param1 as u8)
|
||||
}
|
||||
INST_JPX => {
|
||||
JPX(param1 as u8, param2)
|
||||
}
|
||||
_ => {
|
||||
XXXXERRORINSTRUCTION
|
||||
}
|
||||
INST_ADDI => ADDI(param1 as u8),
|
||||
INST_ADD => ADD(param1 as u8, param2 as u8),
|
||||
INST_CLS => CLS,
|
||||
INST_DRW => DRW(param1 as u8, param2 as u8, param3 as u8),
|
||||
INST_CALL => CALL(param1),
|
||||
INST_SYS => SYS(param1),
|
||||
INST_RET => RET,
|
||||
INST_JPA => JPA(param1),
|
||||
INST_JPI => JPI(param1),
|
||||
INST_SEX => SEX(param1 as u8, param2 as u8),
|
||||
INST_SNEB => SNEB(param1 as u8, param2 as u8),
|
||||
INST_SCD => SCD(param1 as u8),
|
||||
INST_STR => STR(param1 as u8),
|
||||
INST_SCL => SCL,
|
||||
INST_EXIT => EXIT,
|
||||
INST_LOW => LOW,
|
||||
INST_HIGH => HIGH,
|
||||
INST_SEY => SEY(param1 as u8, param2 as u8),
|
||||
INST_LDRY => LDRY(param1 as u8, param2 as u8),
|
||||
INST_LDR => LDR(param1 as u8, param2 as u8),
|
||||
INST_OR => OR(param1 as u8, param2 as u8),
|
||||
INST_AND => AND(param1 as u8, param2 as u8),
|
||||
INST_ORY => ORY(param1 as u8, param2 as u8),
|
||||
INST_ADDR => ADDR(param1 as u8, param2 as u8),
|
||||
INST_SUB => SUB(param1 as u8, param2 as u8),
|
||||
INST_SHR => SHR(param1 as u8, param2 as u8),
|
||||
INST_SHL => SHL(param1 as u8, param2 as u8),
|
||||
INST_SUBC => SUBC(param1 as u8, param2 as u8),
|
||||
INST_SNEY => SNEY(param1 as u8, param2 as u8),
|
||||
INST_LDIA => LDIA(param1),
|
||||
INST_RND => RND(param1 as u8, param2 as u8),
|
||||
INST_SKP => SKP(param1 as u8),
|
||||
INST_SKNP => SKNP(param1 as u8),
|
||||
INST_LDRD => LDRD(param1 as u8),
|
||||
INST_LDRK => LDRK(param1 as u8),
|
||||
INST_LDRI => LDRI(param1 as u8),
|
||||
INST_BCD => BCD(param1 as u8),
|
||||
INST_LDF => LDFX(param1 as u8),
|
||||
INST_LDF2 => LDF2(param1 as u8),
|
||||
INST_LDIX => LDIX(param1 as u8),
|
||||
INST_LIDR => LIDR(param1 as u8),
|
||||
INST_LDIS => LDIS(param1 as u8),
|
||||
INST_LDD => LDD(param1 as u8),
|
||||
INST_JPX => JPX(param1 as u8, param2),
|
||||
_ => XXXXERRORINSTRUCTION,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -564,27 +493,62 @@ impl Chip8CpuInstructions {
|
||||
Chip8CpuInstructions::RET => 0x00EE,
|
||||
Chip8CpuInstructions::JPA(new_addr) => 0x1000 | (new_addr & 0x0FFF),
|
||||
Chip8CpuInstructions::CALL(address) => 0x2000 | (address & 0x0FFF),
|
||||
Chip8CpuInstructions::SEX(vx_register, byte) => 0x3000 | ((*vx_register as u16) << 8) | (*byte as u16),
|
||||
Chip8CpuInstructions::SNEB(vx_register, byte) => 0x4000 | ((*vx_register as u16) << 8) | (*byte as u16),
|
||||
Chip8CpuInstructions::SEY(x_register, y_register) => 0x5000 | ((*x_register as u16) << 8) | ((*y_register as u16) << 4),
|
||||
Chip8CpuInstructions::LDR(x_register, byte) => 0x6000 | ((*x_register as u16) << 8) | (*byte as u16),
|
||||
Chip8CpuInstructions::ADD(x_register, byte) => 0x7000 | ((*x_register as u16) << 8) | (*byte as u16),
|
||||
Chip8CpuInstructions::LDR_Y(x_register, y_register) => 0x8000 | ((*x_register as u16) << 8) | ((*y_register as u16) << 4),
|
||||
Chip8CpuInstructions::OR(x_register, y_register) => 0x8001 | ((*x_register as u16) << 8) | ((*y_register as u16) << 4),
|
||||
Chip8CpuInstructions::AND(x_register, y_register) => 0x8002 | ((*x_register as u16) << 8) | ((*y_register as u16) << 4),
|
||||
Chip8CpuInstructions::ORY(x_register, y_register) => 0x8003 | ((*x_register as u16) << 8) | ((*y_register as u16) << 4),
|
||||
Chip8CpuInstructions::ADDR(x_register, y_register) => 0x8004 | ((*x_register as u16) << 8) | ((*y_register as u16) << 4),
|
||||
Chip8CpuInstructions::SUB(x_register, y_register) => 0x8005 | ((*x_register as u16) << 8) | ((*y_register as u16) << 4),
|
||||
Chip8CpuInstructions::SHR(x_register, y_register) => 0x8006 | ((*x_register as u16) << 8) | ((*y_register as u16) << 4),
|
||||
Chip8CpuInstructions::SUBC(x_register, y_register) => 0x8007 | ((*x_register as u16) << 8) | ((*y_register as u16) << 4),
|
||||
Chip8CpuInstructions::SHL(x_register, y_register) => 0x800E | ((*x_register as u16) << 8) | ((*y_register as u16) << 4),
|
||||
Chip8CpuInstructions::SNEY(x_register, y_register) => 0x9000 | ((*x_register as u16) << 8) | ((*y_register as u16) << 4),
|
||||
Chip8CpuInstructions::SEX(vx_register, byte) => {
|
||||
0x3000 | ((*vx_register as u16) << 8) | (*byte as u16)
|
||||
}
|
||||
Chip8CpuInstructions::SNEB(vx_register, byte) => {
|
||||
0x4000 | ((*vx_register as u16) << 8) | (*byte as u16)
|
||||
}
|
||||
Chip8CpuInstructions::SEY(x_register, y_register) => {
|
||||
0x5000 | ((*x_register as u16) << 8) | ((*y_register as u16) << 4)
|
||||
}
|
||||
Chip8CpuInstructions::LDR(x_register, byte) => {
|
||||
0x6000 | ((*x_register as u16) << 8) | (*byte as u16)
|
||||
}
|
||||
Chip8CpuInstructions::ADD(x_register, byte) => {
|
||||
0x7000 | ((*x_register as u16) << 8) | (*byte as u16)
|
||||
}
|
||||
Chip8CpuInstructions::LDRY(x_register, y_register) => {
|
||||
0x8000 | ((*x_register as u16) << 8) | ((*y_register as u16) << 4)
|
||||
}
|
||||
Chip8CpuInstructions::OR(x_register, y_register) => {
|
||||
0x8001 | ((*x_register as u16) << 8) | ((*y_register as u16) << 4)
|
||||
}
|
||||
Chip8CpuInstructions::AND(x_register, y_register) => {
|
||||
0x8002 | ((*x_register as u16) << 8) | ((*y_register as u16) << 4)
|
||||
}
|
||||
Chip8CpuInstructions::ORY(x_register, y_register) => {
|
||||
0x8003 | ((*x_register as u16) << 8) | ((*y_register as u16) << 4)
|
||||
}
|
||||
Chip8CpuInstructions::ADDR(x_register, y_register) => {
|
||||
0x8004 | ((*x_register as u16) << 8) | ((*y_register as u16) << 4)
|
||||
}
|
||||
Chip8CpuInstructions::SUB(x_register, y_register) => {
|
||||
0x8005 | ((*x_register as u16) << 8) | ((*y_register as u16) << 4)
|
||||
}
|
||||
Chip8CpuInstructions::SHR(x_register, y_register) => {
|
||||
0x8006 | ((*x_register as u16) << 8) | ((*y_register as u16) << 4)
|
||||
}
|
||||
Chip8CpuInstructions::SUBC(x_register, y_register) => {
|
||||
0x8007 | ((*x_register as u16) << 8) | ((*y_register as u16) << 4)
|
||||
}
|
||||
Chip8CpuInstructions::SHL(x_register, y_register) => {
|
||||
0x800E | ((*x_register as u16) << 8) | ((*y_register as u16) << 4)
|
||||
}
|
||||
Chip8CpuInstructions::SNEY(x_register, y_register) => {
|
||||
0x9000 | ((*x_register as u16) << 8) | ((*y_register as u16) << 4)
|
||||
}
|
||||
Chip8CpuInstructions::LDIA(addr) => 0xA000 | addr,
|
||||
Chip8CpuInstructions::JPI(addr) => 0xB000 | addr,
|
||||
JPX(x_register, addr)=> (0xb000 | (*x_register as u16) << 8) | *addr,
|
||||
Chip8CpuInstructions::RND(x_register, byte) => 0xC000 | ((*x_register as u16) << 8) | (*byte as u16),
|
||||
JPX(x_register, addr) => (0xb000 | (*x_register as u16) << 8) | *addr,
|
||||
Chip8CpuInstructions::RND(x_register, byte) => {
|
||||
0xC000 | ((*x_register as u16) << 8) | (*byte as u16)
|
||||
}
|
||||
Chip8CpuInstructions::DRW(x_register, y_register, height) => {
|
||||
0xD000 | ((*x_register as u16) << 8) | ((*y_register as u16) << 4) | (*height as u16)
|
||||
0xD000
|
||||
| ((*x_register as u16) << 8)
|
||||
| ((*y_register as u16) << 4)
|
||||
| (*height as u16)
|
||||
}
|
||||
Chip8CpuInstructions::SKP(x_register) => 0xE09E | ((*x_register as u16) << 8),
|
||||
Chip8CpuInstructions::SKNP(x_register) => 0xE0A1 | ((*x_register as u16) << 8),
|
||||
@@ -621,18 +585,10 @@ impl Chip8CpuInstructions {
|
||||
let last_nibble = (input & 0xF) as u8;
|
||||
|
||||
match input {
|
||||
0x00C0..=0x00CF => {
|
||||
match quirk_mode {
|
||||
QuirkMode::Chip8 => {
|
||||
XXXXERRORINSTRUCTION
|
||||
}
|
||||
QuirkMode::XOChip => {
|
||||
SCD(last_nibble)
|
||||
}
|
||||
QuirkMode::SChipModern => {
|
||||
SCD(last_nibble)
|
||||
}
|
||||
}
|
||||
0x00C0..=0x00CF => match quirk_mode {
|
||||
QuirkMode::Chip8 => XXXXERRORINSTRUCTION,
|
||||
QuirkMode::XOChip => SCD(last_nibble),
|
||||
QuirkMode::SChipModern => SCD(last_nibble),
|
||||
},
|
||||
0x00E0 => Chip8CpuInstructions::CLS,
|
||||
0x00EE => Chip8CpuInstructions::RET,
|
||||
@@ -642,47 +598,23 @@ impl Chip8CpuInstructions {
|
||||
// does not exist on Chip8
|
||||
XXXXERRORINSTRUCTION
|
||||
}
|
||||
QuirkMode::XOChip => {
|
||||
Chip8CpuInstructions::SCR
|
||||
}
|
||||
QuirkMode::SChipModern => {
|
||||
Chip8CpuInstructions::SCR
|
||||
}
|
||||
QuirkMode::XOChip => Chip8CpuInstructions::SCR,
|
||||
QuirkMode::SChipModern => Chip8CpuInstructions::SCR,
|
||||
}
|
||||
},
|
||||
}
|
||||
0x00FC => Chip8CpuInstructions::SCL,
|
||||
0x00FD => Chip8CpuInstructions::EXIT,
|
||||
0x00FE => {
|
||||
match quirk_mode {
|
||||
QuirkMode::Chip8 => {
|
||||
XXXXERRORINSTRUCTION
|
||||
}
|
||||
QuirkMode::XOChip |
|
||||
QuirkMode::SChipModern => {
|
||||
LOW
|
||||
}
|
||||
}
|
||||
},
|
||||
0x00FF => {
|
||||
match quirk_mode {
|
||||
QuirkMode::Chip8 => {
|
||||
XXXXERRORINSTRUCTION
|
||||
},
|
||||
QuirkMode::XOChip |
|
||||
QuirkMode::SChipModern => {
|
||||
HIGH
|
||||
}
|
||||
}
|
||||
0x00FE => match quirk_mode {
|
||||
QuirkMode::Chip8 => XXXXERRORINSTRUCTION,
|
||||
QuirkMode::XOChip | QuirkMode::SChipModern => LOW,
|
||||
},
|
||||
0x0000..=0x0FFF => {
|
||||
match quirk_mode {
|
||||
QuirkMode::Chip8 => {
|
||||
Chip8CpuInstructions::SYS(addr_param)
|
||||
}
|
||||
QuirkMode::XOChip | QuirkMode::SChipModern => {
|
||||
XXXXERRORINSTRUCTION
|
||||
}
|
||||
}
|
||||
0x00FF => match quirk_mode {
|
||||
QuirkMode::Chip8 => XXXXERRORINSTRUCTION,
|
||||
QuirkMode::XOChip | QuirkMode::SChipModern => HIGH,
|
||||
},
|
||||
0x0000..=0x0FFF => match quirk_mode {
|
||||
QuirkMode::Chip8 => Chip8CpuInstructions::SYS(addr_param),
|
||||
QuirkMode::XOChip | QuirkMode::SChipModern => XXXXERRORINSTRUCTION,
|
||||
},
|
||||
0x1000..=0x1FFF => JPA(addr_param),
|
||||
0x2000..=0x2FFF => CALL(addr_param),
|
||||
@@ -692,7 +624,7 @@ impl Chip8CpuInstructions {
|
||||
0x6000..=0x6FFF => LDR(x_param, byte_param),
|
||||
0x7000..=0x7FFF => ADD(x_param, byte_param),
|
||||
0x8000..=0x8FFE => match last_nibble {
|
||||
0x0 => Chip8CpuInstructions::LDR_Y(x_param, y_param),
|
||||
0x0 => Chip8CpuInstructions::LDRY(x_param, y_param),
|
||||
0x1 => Chip8CpuInstructions::OR(x_param, y_param),
|
||||
0x2 => Chip8CpuInstructions::AND(x_param, y_param),
|
||||
0x3 => Chip8CpuInstructions::ORY(x_param, y_param),
|
||||
@@ -701,8 +633,8 @@ impl Chip8CpuInstructions {
|
||||
0x6 => Chip8CpuInstructions::SHR(x_param, y_param),
|
||||
0x7 => Chip8CpuInstructions::SUBC(x_param, y_param),
|
||||
0xE => Chip8CpuInstructions::SHL(x_param, y_param),
|
||||
_ => XXXXERRORINSTRUCTION
|
||||
}
|
||||
_ => XXXXERRORINSTRUCTION,
|
||||
},
|
||||
0x9000..=0x9FF0 if input & 0x01 == 0 => Chip8CpuInstructions::SNEY(x_param, y_param),
|
||||
0xA000..=0xAFFF => Chip8CpuInstructions::LDIA(addr_param),
|
||||
0xB000..=0xBFFF => Chip8CpuInstructions::JPI(addr_param),
|
||||
@@ -711,8 +643,8 @@ impl Chip8CpuInstructions {
|
||||
0xE09E..=0xEFA1 => match last_byte {
|
||||
0x9E => Chip8CpuInstructions::SKP(ubln),
|
||||
0xA1 => Chip8CpuInstructions::SKNP(ubln),
|
||||
_ => XXXXERRORINSTRUCTION
|
||||
}
|
||||
_ => XXXXERRORINSTRUCTION,
|
||||
},
|
||||
0xF007..=0xFF65 => match last_byte {
|
||||
0x07 => Chip8CpuInstructions::LDRD(ubln),
|
||||
0x0A => Chip8CpuInstructions::LDRK(ubln),
|
||||
@@ -726,9 +658,9 @@ impl Chip8CpuInstructions {
|
||||
0x65 => Chip8CpuInstructions::LDRI(ubln),
|
||||
0x75 => Chip8CpuInstructions::STR(ubln),
|
||||
0x85 => Chip8CpuInstructions::LIDR(ubln),
|
||||
_ => XXXXERRORINSTRUCTION
|
||||
}
|
||||
_ => XXXXERRORINSTRUCTION
|
||||
_ => XXXXERRORINSTRUCTION,
|
||||
},
|
||||
_ => XXXXERRORINSTRUCTION,
|
||||
}
|
||||
}
|
||||
pub fn execute(&self, input: &mut Chip8Computer) -> Chip8Computer {
|
||||
@@ -788,10 +720,13 @@ impl Chip8CpuInstructions {
|
||||
}
|
||||
// 0x7xkk Set Vx = Vx + kk
|
||||
Chip8CpuInstructions::ADD(vx_register, byte) => {
|
||||
input.registers.poke(*vx_register, (input.registers.peek(*vx_register) as u16 + *byte as u16) as u8);
|
||||
input.registers.poke(
|
||||
*vx_register,
|
||||
(input.registers.peek(*vx_register) as u16 + *byte as u16) as u8,
|
||||
);
|
||||
}
|
||||
// 0x8xy0 Set value of Vy in Vx
|
||||
Chip8CpuInstructions::LDR_Y(x, y) => {
|
||||
Chip8CpuInstructions::LDRY(x, y) => {
|
||||
input.registers.poke(*x, input.registers.peek(*y));
|
||||
}
|
||||
// 0x8xy1 Set Vx = Vx OR Vy
|
||||
@@ -881,14 +816,11 @@ impl Chip8CpuInstructions {
|
||||
// If Vy > Vx, then VF is set to 1, otherwise 0. Then Vx is subtracted from Vy, and the results stored in Vx.
|
||||
let y_register = input.registers.peek(*y);
|
||||
let x_register = input.registers.peek(*x);
|
||||
let mut value_to_poke = 0;
|
||||
|
||||
let new_value = if y_register < x_register {
|
||||
value_to_poke = (y_register as u16 + 256) - x_register as u16;
|
||||
0
|
||||
let (new_value, value_to_poke) = if y_register < x_register {
|
||||
(0, (y_register as u16 + 256) - x_register as u16)
|
||||
} else {
|
||||
value_to_poke = (y_register - x_register) as u16;
|
||||
1
|
||||
(1, (y_register - x_register) as u16)
|
||||
};
|
||||
input.registers.poke(*x, value_to_poke as u8);
|
||||
input.registers.poke(0xf, new_value);
|
||||
@@ -936,7 +868,9 @@ impl Chip8CpuInstructions {
|
||||
// Jump to location nnn + V0.
|
||||
//
|
||||
// The program counter is set to nnn plus the value of V0.
|
||||
input.registers.poke_pc(input.registers.peek(0) as u16 + addr);
|
||||
input
|
||||
.registers
|
||||
.poke_pc(input.registers.peek(0) as u16 + addr);
|
||||
}
|
||||
// 0xBxnn Jump to Xnn+Vx
|
||||
JPX(vx_register, addr) => {
|
||||
@@ -978,26 +912,35 @@ impl Chip8CpuInstructions {
|
||||
let y_offset = input.registers.peek(*y) as u16;
|
||||
if input.video_memory.is_highres() {
|
||||
// if n == 0 we have a 16 row sprite (font maybe)
|
||||
let actual_num_loops = if *n == 0u8 {
|
||||
16
|
||||
} else {
|
||||
*n
|
||||
};
|
||||
let actual_num_loops = if *n == 0u8 { 16 } else { *n };
|
||||
for byte_index in 0..actual_num_loops {
|
||||
let current_byte = input.memory.peek(byte_index as u16 + source_memory_offset);
|
||||
let next_byte = input.memory.peek(byte_index as u16 + 1u16 + source_memory_offset);
|
||||
let current_byte =
|
||||
input.memory.peek(byte_index as u16 + source_memory_offset);
|
||||
let next_byte = input
|
||||
.memory
|
||||
.peek(byte_index as u16 + 1u16 + source_memory_offset);
|
||||
let x_offset = (x_offset + byte_index as u16) * 64;
|
||||
for bit_index in 0..8 {
|
||||
input.video_memory.poke(x_offset + (y_offset + bit_index as u16), (current_byte & (0x80 >> bit_index)) != 0);
|
||||
input.video_memory.poke(x_offset + (y_offset + bit_index as u16) + 8, (current_byte & (0x80 >> bit_index)) != 0);
|
||||
input.video_memory.poke(
|
||||
x_offset + (y_offset + bit_index as u16),
|
||||
(current_byte & (0x80 >> bit_index)) != 0,
|
||||
);
|
||||
input.video_memory.poke(
|
||||
x_offset + (y_offset + bit_index as u16) + 8,
|
||||
(next_byte & (0x80 >> bit_index)) != 0,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for byte_index in 0..*n {
|
||||
let current_byte = input.memory.peek(byte_index as u16 + source_memory_offset);
|
||||
let current_byte =
|
||||
input.memory.peek(byte_index as u16 + source_memory_offset);
|
||||
let x_offset: u16 = (x_offset + byte_index as u16) * 64;
|
||||
for bit_index in 0..8 {
|
||||
input.video_memory.poke(x_offset + (y_offset + bit_index as u16), (current_byte & (0x80 >> bit_index)) != 0);
|
||||
input.video_memory.poke(
|
||||
x_offset + (y_offset + bit_index as u16),
|
||||
(current_byte & (0x80 >> bit_index)) != 0,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1103,7 +1046,8 @@ impl Chip8CpuInstructions {
|
||||
let units = to_convert % 10;
|
||||
|
||||
// Convert to BCD
|
||||
let result = ((hundreds as u16) << 8) | units as u16 | ((tens as u16) << 4) | units as u16;
|
||||
let result =
|
||||
((hundreds as u16) << 8) | units as u16 | ((tens as u16) << 4) | units as u16;
|
||||
// write them to the memory pointed to by I, I+1, and I+2
|
||||
let target_start_offset = input.registers.peek_i();
|
||||
input.memory.poke(target_start_offset, hundreds);
|
||||
@@ -1117,7 +1061,9 @@ impl Chip8CpuInstructions {
|
||||
// starting at the address in I.
|
||||
let offset = input.registers.peek_i();
|
||||
for i in 0..=*x {
|
||||
input.memory.poke(offset + i as u16, input.registers.peek(i));
|
||||
input
|
||||
.memory
|
||||
.poke(offset + i as u16, input.registers.peek(i));
|
||||
}
|
||||
input.registers.poke_i(offset + 1);
|
||||
}
|
||||
@@ -1137,78 +1083,58 @@ impl Chip8CpuInstructions {
|
||||
input.registers.poke_i(offset + 1);
|
||||
}
|
||||
Chip8CpuInstructions::XXXXERRORINSTRUCTION => {}
|
||||
Chip8CpuInstructions::SCD(x) => {
|
||||
match input.quirk_mode {
|
||||
QuirkMode::Chip8 => {
|
||||
debug!("Attempt to execute SCD in Chip8 Mode");
|
||||
}
|
||||
QuirkMode::XOChip |
|
||||
QuirkMode::SChipModern => {
|
||||
input.video_memory.scroll_down(*x as i32);
|
||||
}
|
||||
Chip8CpuInstructions::SCD(x) => match input.quirk_mode {
|
||||
QuirkMode::Chip8 => {
|
||||
debug!("Attempt to execute SCD in Chip8 Mode");
|
||||
}
|
||||
}
|
||||
Chip8CpuInstructions::SCR => {
|
||||
match input.quirk_mode {
|
||||
QuirkMode::Chip8 => {
|
||||
debug!("Attempt to execute SCR in Chip8 Mode");
|
||||
}
|
||||
QuirkMode::XOChip |
|
||||
QuirkMode::SChipModern => {
|
||||
input.video_memory.scroll_right();
|
||||
}
|
||||
QuirkMode::XOChip | QuirkMode::SChipModern => {
|
||||
input.video_memory.scroll_down(*x as i32);
|
||||
}
|
||||
}
|
||||
Chip8CpuInstructions::SCL => {
|
||||
match input.quirk_mode {
|
||||
QuirkMode::Chip8 => {
|
||||
debug!("Attempt to execute SCL in Chip8 Mode");
|
||||
}
|
||||
QuirkMode::XOChip |
|
||||
QuirkMode::SChipModern => {
|
||||
input.video_memory.scroll_left();
|
||||
}
|
||||
|
||||
},
|
||||
Chip8CpuInstructions::SCR => match input.quirk_mode {
|
||||
QuirkMode::Chip8 => {
|
||||
debug!("Attempt to execute SCR in Chip8 Mode");
|
||||
}
|
||||
}
|
||||
Chip8CpuInstructions::LOW => {
|
||||
match input.quirk_mode {
|
||||
QuirkMode::Chip8 => {
|
||||
debug!("ATTEMPT TO SET LOWRES IN CHIP8MODE");
|
||||
}
|
||||
QuirkMode::XOChip | QuirkMode::SChipModern => {
|
||||
input.video_memory.set_lowres();
|
||||
|
||||
}
|
||||
QuirkMode::XOChip | QuirkMode::SChipModern => {
|
||||
input.video_memory.scroll_right();
|
||||
}
|
||||
}
|
||||
Chip8CpuInstructions::HIGH => {
|
||||
match input.quirk_mode {
|
||||
QuirkMode::Chip8 => {
|
||||
debug!("ATTEMPT TO SET HIGHRES IN CHIP8MODE");
|
||||
}
|
||||
QuirkMode::XOChip | QuirkMode::SChipModern => {
|
||||
input.video_memory.set_highres();
|
||||
|
||||
}
|
||||
},
|
||||
Chip8CpuInstructions::SCL => match input.quirk_mode {
|
||||
QuirkMode::Chip8 => {
|
||||
debug!("Attempt to execute SCL in Chip8 Mode");
|
||||
}
|
||||
}
|
||||
Chip8CpuInstructions::EXIT => {
|
||||
match input.quirk_mode {
|
||||
QuirkMode::Chip8 => {
|
||||
debug!("ATTEMPT TO EXIT FROM CHIP8 INTERPRETER");
|
||||
}
|
||||
QuirkMode::XOChip |
|
||||
QuirkMode::SChipModern => {
|
||||
println!("EXIT INTERPRETER");
|
||||
}
|
||||
QuirkMode::XOChip | QuirkMode::SChipModern => {
|
||||
input.video_memory.scroll_left();
|
||||
}
|
||||
}
|
||||
},
|
||||
Chip8CpuInstructions::LOW => match input.quirk_mode {
|
||||
QuirkMode::Chip8 => {
|
||||
debug!("ATTEMPT TO SET LOWRES IN CHIP8MODE");
|
||||
}
|
||||
QuirkMode::XOChip | QuirkMode::SChipModern => {
|
||||
input.video_memory.set_lowres();
|
||||
}
|
||||
},
|
||||
Chip8CpuInstructions::HIGH => match input.quirk_mode {
|
||||
QuirkMode::Chip8 => {
|
||||
debug!("ATTEMPT TO SET HIGHRES IN CHIP8MODE");
|
||||
}
|
||||
QuirkMode::XOChip | QuirkMode::SChipModern => {
|
||||
input.video_memory.set_highres();
|
||||
}
|
||||
},
|
||||
Chip8CpuInstructions::EXIT => match input.quirk_mode {
|
||||
QuirkMode::Chip8 => {
|
||||
debug!("ATTEMPT TO EXIT FROM CHIP8 INTERPRETER");
|
||||
}
|
||||
QuirkMode::XOChip | QuirkMode::SChipModern => {
|
||||
println!("EXIT INTERPRETER");
|
||||
}
|
||||
},
|
||||
Chip8CpuInstructions::LDF2(x) => {
|
||||
match input.quirk_mode {
|
||||
QuirkMode::Chip8 => {
|
||||
debug!("ATTEMPT TO LDF2 IN CHIP8MODE");
|
||||
|
||||
}
|
||||
QuirkMode::XOChip | QuirkMode::SChipModern => {
|
||||
println!("POINTING TO FONT AT {x:02x}");
|
||||
@@ -1217,34 +1143,26 @@ impl Chip8CpuInstructions {
|
||||
}
|
||||
}
|
||||
}
|
||||
Chip8CpuInstructions::STR(x) => {
|
||||
match input.quirk_mode {
|
||||
QuirkMode::Chip8 => {
|
||||
debug!("ATTEMPT TO STORE RPL IN CHIP8MODE");
|
||||
}
|
||||
QuirkMode::XOChip |
|
||||
QuirkMode::SChipModern => {
|
||||
println!("STORING FROM RPL FOR {x}");
|
||||
}
|
||||
Chip8CpuInstructions::STR(x) => match input.quirk_mode {
|
||||
QuirkMode::Chip8 => {
|
||||
debug!("ATTEMPT TO STORE RPL IN CHIP8MODE");
|
||||
}
|
||||
}
|
||||
Chip8CpuInstructions::LIDR(x) => {
|
||||
match input.quirk_mode {
|
||||
QuirkMode::Chip8 => {
|
||||
debug!("ATTEMPT TO LOAD RPL IN CHIP8MODE");
|
||||
}
|
||||
QuirkMode::XOChip |
|
||||
QuirkMode::SChipModern => {
|
||||
println!("LOADING FROM RPL FOR {x}");
|
||||
|
||||
}
|
||||
QuirkMode::XOChip | QuirkMode::SChipModern => {
|
||||
println!("STORING FROM RPL FOR {x}");
|
||||
}
|
||||
}
|
||||
},
|
||||
Chip8CpuInstructions::LIDR(x) => match input.quirk_mode {
|
||||
QuirkMode::Chip8 => {
|
||||
debug!("ATTEMPT TO LOAD RPL IN CHIP8MODE");
|
||||
}
|
||||
QuirkMode::XOChip | QuirkMode::SChipModern => {
|
||||
println!("LOADING FROM RPL FOR {x}");
|
||||
}
|
||||
},
|
||||
SCU(x) => {
|
||||
println!("SCROLL SCREEN UP {x} ROWS");
|
||||
match input.quirk_mode {
|
||||
QuirkMode::Chip8 |
|
||||
QuirkMode::SChipModern => {
|
||||
QuirkMode::Chip8 | QuirkMode::SChipModern => {
|
||||
debug!("Attempt to run SCU outside XO mode");
|
||||
}
|
||||
QuirkMode::XOChip => {
|
||||
|
||||
Reference in New Issue
Block a user