multi: upgrade dependencies, fix a bug with interrupts, implement some missing opcodes

This commit is contained in:
EliseZeroTwo 2023-12-30 09:08:15 -07:00
parent 00850645cd
commit 9e199f8a44
Signed by: elise
GPG key ID: FA8F56FFFE6E8B3E
18 changed files with 1523 additions and 945 deletions

1873
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,12 +1,13 @@
[bindings]
a = "A"
b = "S"
select = "Q"
start = "W"
up = "Up"
down = "Down"
left = "Left"
right = "Right"
pause = "P"
a = "KeyA"
b = "KeyS"
select = "KeyQ"
start = "KeyW"
up = "ArrowUp"
down = "ArrowDown"
left = "ArrowLeft"
right = "ArrowRight"
pause = "KeyP"
exit = "Escape"
log_ops = "L"
log_ops = "KeyL"
dump_memory = "Comma"

View file

@ -8,7 +8,7 @@ proc-macro = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
darling = "0.13.1"
darling = "0.20.3"
proc-macro2 = "1.0.33"
quote = "1.0.10"
syn = "1.0.82"
syn = "2.0.43"

View file

@ -1,8 +1,6 @@
use proc_macro::TokenStream;
use proc_macro2::Ident;
use syn::{
braced, parse::Parse, parse_macro_input, punctuated::Punctuated, Expr, ExprMacro, LitBool,
LitInt, LitStr, Stmt, Token,
braced, parse::Parse, parse_macro_input, punctuated::Punctuated, Expr, LitBool, LitInt, Token,
};
struct OpcodeImpl {
@ -49,6 +47,8 @@ pub fn opcode(item: TokenStream) -> TokenStream {
let OpcodeArgs { name, opcode, readable, extended, implementation } =
parse_macro_input!(item as OpcodeArgs);
let name_s = name.to_string();
let opcode = opcode.base10_parse::<u8>().expect("Failed to parse opcode as u8");
let fn_sig = quote::quote! {
@ -72,7 +72,7 @@ pub fn opcode(item: TokenStream) -> TokenStream {
}*/
let regs = quote::quote! {
log::trace!("-- Registers --\nAF: {:04X}\nBC: {:04X}\nDE: {:04X}\nHL: {:04X}\nSP: {:04X}\nPC: {:04X}\nZero: {}\nSubtract: {}\nHalf-Carry: {}\nCarry: {}", state.registers.get_af(), state.registers.get_bc(), state.registers.get_de(), state.registers.get_hl(), state.registers.get_sp(), state.registers.pc, state.registers.get_zero(), state.registers.get_subtract(), state.registers.get_half_carry(), state.registers.get_carry());
log::info!("\nSTART OF {}\n-- Registers --\nAF: {:04X}\nBC: {:04X}\nDE: {:04X}\nHL: {:04X}\nSP: {:04X}\nPC: {:04X}\nZero: {}\nSubtract: {}\nHalf-Carry: {}\nCarry: {}\n-- Interrupts --\nIME: {}\nIE VBlank: {}\nIE LCD Stat: {}\nIE Timer: {}\nIE Serial: {}\nIE Joypad: {}\nIF VBlank: {}\nIF LCD Stat: {}\nIF Timer: {}\nIF Serial: {}\nIF Joypad: {}\nEND OF {}", #name_s, state.registers.get_af(), state.registers.get_bc(), state.registers.get_de(), state.registers.get_hl(), state.registers.get_sp(), state.registers.pc, state.registers.get_zero(), state.registers.get_subtract(), state.registers.get_half_carry(), state.registers.get_carry(), state.interrupts.ime, state.interrupts.read_ie_vblank(), state.interrupts.read_ie_lcd_stat(), state.interrupts.read_ie_timer(), state.interrupts.read_ie_serial(), state.interrupts.read_ie_joypad(), state.interrupts.read_if_vblank(), state.interrupts.read_if_lcd_stat(), state.interrupts.read_if_timer(), state.interrupts.read_if_serial(), state.interrupts.read_if_joypad(), #name_s);
};
let match_statement = quote::quote! {

View file

@ -9,14 +9,14 @@ edition = "2021"
argh = "0.1.6"
bmp = "0.5.0"
chrono = "0.4.19"
config = "0.11.0"
config = "0.13.4"
deemgee-opcode = { path = "../deemgee-opcode" }
env_logger = "0.9.0"
env_logger = "0.10.1"
log = "0.4.14"
paste = "1.0.6"
pixels = "0.7.0"
pixels = "0.13.0"
serde = { version = "1.0.130", features = ["derive"] }
sha1 = { version = "0.6.0", features = ["std"] }
sha1 = { version = "0.10.6", features = ["std"] }
thiserror = "1.0.30"
winit = { version = "0.25.0", features = ["serde"] }
winit_input_helper = "0.10.0"
winit = { version = "0.29.7", default-features = false, features = ["serde", "rwh_05"] }
winit_input_helper = "0.15.1"

View file

@ -117,7 +117,7 @@ impl Gameboy {
}
fn log_state(&self) {
log::info!("-- Registers --\nAF: {:04X}\nBC: {:04X}\nDE: {:04X}\nHL: {:04X}\nSP: {:04X}\nPC: {:04X}\nZero: {}\nSubtract: {}\nHalf-Carry: {}\nCarry: {}", self.registers.get_af(), self.registers.get_bc(), self.registers.get_de(), self.registers.get_hl(), self.registers.get_sp(), self.registers.pc, self.registers.get_zero(), self.registers.get_subtract(), self.registers.get_half_carry(), self.registers.get_carry());
log::info!("\n-- Registers --\nAF: {:04X}\nBC: {:04X}\nDE: {:04X}\nHL: {:04X}\nSP: {:04X}\nPC: {:04X}\nZero: {}\nSubtract: {}\nHalf-Carry: {}\nCarry: {}\n-- Interrupts --\nIME: {}\nIE VBlank: {}\nIE LCD Stat: {}\nIE Timer: {}\nIE Serial: {}\nIE Joypad: {}\nIF VBlank: {}\nIF LCD Stat: {}\nIF Timer: {}\nIF Serial: {}\nIF Joypad: {}\n", self.registers.get_af(), self.registers.get_bc(), self.registers.get_de(), self.registers.get_hl(), self.registers.get_sp(), self.registers.pc, self.registers.get_zero(), self.registers.get_subtract(), self.registers.get_half_carry(), self.registers.get_carry(), self.interrupts.ime, self.interrupts.read_ie_vblank(), self.interrupts.read_ie_lcd_stat(), self.interrupts.read_ie_timer(), self.interrupts.read_ie_serial(), self.interrupts.read_ie_joypad(), self.interrupts.read_if_vblank(), self.interrupts.read_if_lcd_stat(), self.interrupts.read_if_timer(), self.interrupts.read_if_serial(), self.interrupts.read_if_joypad());
}
pub fn tick(&mut self) -> bool {
@ -189,7 +189,12 @@ impl Gameboy {
log::info!("Continuing");
exit = false;
}
"s" | "step" => {
"p" | "pause" => {
self.single_step = true;
log::info!("Single step activated");
exit = false;
}
"s" | "step" | "" => {
self.log_next_opcode();
exit = false;
}
@ -286,7 +291,7 @@ impl Gameboy {
0xFF06 => self.timer.tma,
0xFF07 => self.timer.read_tac(),
0xFF08..=0xFF0E => 0, // Unused
0xFF0F => self.interrupts.interrupt_enable,
0xFF0F => self.interrupts.interrupt_flag,
0xFF10 => self.sound.nr10,
0xFF11 => self.sound.nr11,
0xFF12 => self.sound.nr12,
@ -346,7 +351,7 @@ impl Gameboy {
0xFF06 => self.timer.tma = value,
0xFF07 => self.timer.write_tac(value),
0xFF08..=0xFF0E => {} // Unused
0xFF0F => self.interrupts.interrupt_enable = value & 0b1_1111,
0xFF0F => self.interrupts.interrupt_flag = value & 0b1_1111,
0xFF10 => self.sound.nr10 = value,
0xFF11 => self.sound.nr11 = value,
0xFF12 => self.sound.nr12 = value,
@ -403,6 +408,34 @@ impl Gameboy {
}
}
pub fn dump_memory(&self) -> [u8; 0xFFFF] {
let mut out = [0u8; 0xFFFF];
for address in 0..0xFFFF {
out[address as usize] = match address {
0..=0xFF if !self.memory.bootrom_disabled => self.memory.bootrom[address as usize],
0..=0x7FFF => match self.cartridge.as_ref() {
Some(mapper) => mapper.read_rom_u8(address),
None => 0,
},
0x8000..=0x9FFF => self.ppu.cpu_read_vram(address),
0xA000..=0xBFFF => match self.cartridge.as_ref() {
Some(mapper) => mapper.read_eram_u8(address),
None => 0,
},
0xC000..=0xDFFF => self.memory.wram[address as usize - 0xC000],
0xE000..=0xFDFF => self.memory.wram[address as usize - 0xE000],
0xFE00..=0xFE9F => self.ppu.cpu_read_oam(address),
0xFEA0..=0xFEFF => 0,
0xFF00..=0xFF7F => self.cpu_read_io(address),
0xFF80..=0xFFFE => self.memory.hram[address as usize - 0xFF80],
0xFFFF => self.interrupts.interrupt_enable,
};
}
out
}
fn internal_cpu_read_u8(&self, address: u16) -> u8 {
if self.dma.remaining_cycles == 0 {
match address {

View file

@ -40,6 +40,7 @@ macro_rules! define_flag {
pub enum CycleResult {
NeedsMore,
Finished,
FinishedChangedPc,
}
#[derive(Debug, Default)]
@ -213,6 +214,7 @@ pub fn tick_cpu(state: &mut Gameboy) {
0x04 => alu::inc_b,
0x05 => alu::dec_b,
0x06 => load_store_move::ld_b_imm_u8,
0x07 => alu::rlca,
0x08 => load_store_move::ld_deref_imm_u16_sp,
0x09 => alu::add_hl_bc,
0x0a => load_store_move::ld_a_deref_bc,
@ -220,6 +222,7 @@ pub fn tick_cpu(state: &mut Gameboy) {
0x0c => alu::inc_c,
0x0d => alu::dec_c,
0x0e => load_store_move::ld_c_imm_u8,
0x0F => alu::rrca,
0x11 => load_store_move::ld_de_imm_u16,
0x12 => load_store_move::ld_deref_de_a,
0x13 => alu::inc_de,
@ -242,6 +245,7 @@ pub fn tick_cpu(state: &mut Gameboy) {
0x24 => alu::inc_h,
0x25 => alu::dec_h,
0x26 => load_store_move::ld_h_imm_u8,
0x27 => alu::daa,
0x28 => flow::jr_z_i8,
0x29 => alu::add_hl_hl,
0x2a => load_store_move::ld_a_hl_plus,
@ -413,7 +417,7 @@ pub fn tick_cpu(state: &mut Gameboy) {
0xD0 => flow::ret_nc,
0xD1 => load_store_move::pop_de,
0xD2 => flow::jp_nc_u16,
0xD3 => panic!("Executing bad opcode {:#02X}", opcode),
0xD3 => panic!("Executing bad opcode {:#02X} @ PC={:#04X}", opcode, state.registers.pc),
0xD4 => flow::call_nc_u16,
0xD5 => load_store_move::push_de,
0xD6 => alu::sub_a_imm_u8,
@ -421,21 +425,25 @@ pub fn tick_cpu(state: &mut Gameboy) {
0xD8 => flow::ret_c,
0xD9 => flow::reti,
0xDA => flow::jp_c_u16,
0xDB => panic!("Executing bad opcode {:#02X}", opcode),
0xDB => panic!("Executing bad opcode {:#02X} @ PC={:#04X}", opcode, state.registers.pc),
0xDC => flow::call_c_u16,
0xDD => panic!("Executing bad opcode {:#02X}", opcode),
0xDD => panic!("Executing bad opcode {:#02X} @ PC={:#04X}", opcode, state.registers.pc),
0xDE => alu::sbc_a_imm_u8,
0xDF => flow::rst_0x18,
0xE0 => load_store_move::ldh_imm_u8_a,
0xE1 => load_store_move::pop_hl,
0xE2 => load_store_move::ldh_deref_c_a,
0xE3 | 0xE4 => panic!("Executing bad opcode {:#02X}", opcode),
0xE3 | 0xE4 => {
panic!("Executing bad opcode {:#02X} @ PC={:#04X}", opcode, state.registers.pc)
}
0xE5 => load_store_move::push_hl,
0xE6 => alu::and_a_imm_u8,
0xE7 => flow::rst_0x20,
0xE9 => flow::jp_hl,
0xEA => load_store_move::ld_deref_imm_u16_a,
0xEB | 0xEC | 0xED => panic!("Executing bad opcode {:#02X}", opcode),
0xEB | 0xEC | 0xED => {
panic!("Executing bad opcode {:#02X} @ PC={:#04X}", opcode, state.registers.pc)
}
0xEE => alu::xor_a_imm_u8,
0xEF => flow::rst_0x28,
0xF0 => load_store_move::ldh_a_imm_u8,
@ -449,7 +457,9 @@ pub fn tick_cpu(state: &mut Gameboy) {
0xF9 => load_store_move::ld_sp_hl,
0xFA => load_store_move::ld_a_deref_imm_u16,
0xFB => misc::ei,
0xFC | 0xFD => panic!("Executing bad opcode {:#02X}", opcode),
0xFC | 0xFD => {
panic!("Executing bad opcode {:#02X} @ PC={:#04X}", opcode, state.registers.pc)
}
0xFE => alu::cp_a_imm_u8,
0xFF => flow::rst_0x38,
unknown => {
@ -464,14 +474,16 @@ pub fn tick_cpu(state: &mut Gameboy) {
result
};
if result == CycleResult::Finished {
if result == CycleResult::Finished || result == CycleResult::FinishedChangedPc {
if state.used_halt_bug {
state.registers.pc = state.registers.pc.overflowing_add(1).0;
}
match state.registers.opcode_bytecount {
Some(len) => state.registers.pc = state.registers.pc.overflowing_add(len as u16).0,
None => panic!("Forgot to set opcode len"),
if result != CycleResult::FinishedChangedPc {
match state.registers.opcode_bytecount {
Some(len) => state.registers.pc = state.registers.pc.overflowing_add(len as u16).0,
None => panic!("Forgot to set opcode len"),
}
}
if !state.registers.mem_op_happened {

View file

@ -877,3 +877,60 @@ opcode!(add_hl_sp, 0x39, "ADD HL, SP", false, {
CycleResult::Finished
}
});
opcode!(rlca, 0x7, "RLCA", false, {
0 => {
let carry = state.registers.a >> 7 == 1;
state.registers.a <<= 1;
state.registers.a |= carry as u8;
state.registers.set_zero(state.registers.a == 0);
state.registers.set_subtract(false);
state.registers.set_half_carry(false);
state.registers.set_carry(carry);
state.registers.opcode_bytecount = Some(1);
CycleResult::Finished
}
});
opcode!(rrca, 0xF, "RRCA", false, {
0 => {
let carry = state.registers.a & 0b1 == 1;
state.registers.a >>= 1;
state.registers.a |= (carry as u8) << 7;
state.registers.set_zero(state.registers.a == 0);
state.registers.set_subtract(false);
state.registers.set_half_carry(false);
state.registers.set_carry(carry);
state.registers.opcode_bytecount = Some(1);
CycleResult::Finished
}
});
opcode!(daa, 0x27, "DAA", false, {
0 => {
let mut value = 0;
let mut carry = false;
if state.registers.get_half_carry() || (!state.registers.get_subtract() && (state.registers.a & 0xF) > 9) {
value |= 0x06;
}
if state.registers.get_carry() || (!state.registers.get_subtract() && state.registers.a > 0x99) {
value |= 0x60;
carry = true;
}
state.registers.a = match state.registers.get_subtract() {
true => state.registers.a.wrapping_sub(value),
false => state.registers.a.wrapping_add(value)
};
state.registers.set_half_carry(false);
state.registers.set_carry(carry);
state.registers.set_zero(state.registers.a == 0);
state.registers.opcode_bytecount = Some(1);
CycleResult::Finished
}
});

View file

@ -477,7 +477,6 @@ opcode!(ret, 0xC9, "RET", false, {
},
3 => {
let address = (state.registers.take_mem() as u16) << 8 | state.registers.take_hold();
println!("RET to {:#04X}", address);
state.registers.pc = address;
state.registers.opcode_bytecount = Some(0);
CycleResult::Finished

View file

@ -19,6 +19,20 @@ pub fn prefixed_handler(state: &mut Gameboy) -> CycleResult {
};
let res: CycleResult = match opcode {
0x00 => rlc_b,
0x01 => rlc_c,
0x02 => rlc_d,
0x03 => rlc_e,
0x04 => rlc_h,
0x05 => rlc_l,
0x07 => rlc_a,
0x08 => rrc_b,
0x09 => rrc_c,
0x0a => rrc_d,
0x0b => rrc_e,
0x0c => rrc_h,
0x0d => rrc_l,
0x0f => rrc_a,
0x10 => rl_b,
0x11 => rl_c,
0x12 => rl_d,
@ -33,6 +47,20 @@ pub fn prefixed_handler(state: &mut Gameboy) -> CycleResult {
0x1c => rr_h,
0x1d => rr_l,
0x1f => rr_a,
0x20 => sra_b,
0x21 => sra_c,
0x22 => sra_d,
0x23 => sra_e,
0x24 => sra_h,
0x25 => sra_l,
0x27 => sra_a,
0x28 => sla_b,
0x29 => sla_c,
0x2a => sla_d,
0x2b => sla_e,
0x2c => sla_h,
0x2d => sla_l,
0x2f => sla_a,
0x30 => swap_b,
0x31 => swap_c,
0x32 => swap_d,
@ -240,7 +268,7 @@ pub fn prefixed_handler(state: &mut Gameboy) -> CycleResult {
0xFE => set_7_deref_hl,
0xFF => set_7_a,
unknown => panic!(
"Unrecognized prefixed opcode: {:#X}\nRegisters: {:#?}",
"Unrecognized prefixed opcode: {:#X}\nRegisters: {:#X?}",
unknown, state.registers
),
}(state);
@ -351,6 +379,64 @@ define_bit_deref_hl!(0x6e, 5);
define_bit_deref_hl!(0x76, 6);
define_bit_deref_hl!(0x7e, 7);
macro_rules! define_rlc_reg {
($op:literal, $reg:ident) => {
paste::paste! {
opcode!([<rlc_ $reg>], $op, std::concat!("RLC ", std::stringify!($reg)), true, {
1 => {
let carry = state.registers.$reg >> 7 == 1;
state.registers.$reg <<= 1;
state.registers.$reg |= carry as u8;
state.registers.set_zero(state.registers.$reg == 0);
state.registers.set_subtract(false);
state.registers.set_half_carry(false);
state.registers.set_carry(carry);
state.registers.opcode_bytecount = Some(2);
CycleResult::Finished
}
});
}
};
}
define_rlc_reg!(0x00, b);
define_rlc_reg!(0x01, c);
define_rlc_reg!(0x02, d);
define_rlc_reg!(0x03, e);
define_rlc_reg!(0x04, h);
define_rlc_reg!(0x05, l);
define_rlc_reg!(0x07, a);
macro_rules! define_rrc_reg {
($op:literal, $reg:ident) => {
paste::paste! {
opcode!([<rrc_ $reg>], $op, std::concat!("RRC ", std::stringify!($reg)), true, {
1 => {
let carry = state.registers.$reg & 0b1 == 1;
state.registers.$reg >>= 1;
state.registers.$reg |= (carry as u8) << 7;
state.registers.set_zero(state.registers.$reg == 0);
state.registers.set_subtract(false);
state.registers.set_half_carry(false);
state.registers.set_carry(carry);
state.registers.opcode_bytecount = Some(2);
CycleResult::Finished
}
});
}
};
}
define_rrc_reg!(0x08, b);
define_rrc_reg!(0x09, c);
define_rrc_reg!(0x0A, d);
define_rrc_reg!(0x0B, e);
define_rrc_reg!(0x0C, h);
define_rrc_reg!(0x0D, l);
define_rrc_reg!(0x0F, a);
macro_rules! define_rl_reg {
($op:literal, $reg:ident) => {
paste::paste! {
@ -383,6 +469,62 @@ define_rl_reg!(0x14, h);
define_rl_reg!(0x15, l);
define_rl_reg!(0x17, a);
macro_rules! define_sla_reg {
($op:literal, $reg:ident) => {
paste::paste! {
opcode!([<sla_ $reg>], $op, std::concat!("SLA ", std::stringify!($reg)), true, {
1 => {
let carry = state.registers.$reg & (0b1 << 7) == 1;
state.registers.$reg = ((state.registers.$reg as i8) << 1) as u8;
state.registers.set_zero(state.registers.$reg == 0);
state.registers.set_subtract(false);
state.registers.set_half_carry(false);
state.registers.set_carry(carry);
state.registers.opcode_bytecount = Some(2);
CycleResult::Finished
}
});
}
};
}
define_sla_reg!(0x20, b);
define_sla_reg!(0x21, c);
define_sla_reg!(0x22, d);
define_sla_reg!(0x23, e);
define_sla_reg!(0x24, h);
define_sla_reg!(0x25, l);
define_sla_reg!(0x27, a);
macro_rules! define_sra_reg {
($op:literal, $reg:ident) => {
paste::paste! {
opcode!([<sra_ $reg>], $op, std::concat!("SRA ", std::stringify!($reg)), true, {
1 => {
let carry = state.registers.$reg & 0b1 == 1;
state.registers.$reg = ((state.registers.$reg as i8) >> 1) as u8;
state.registers.set_zero(state.registers.$reg == 0);
state.registers.set_subtract(false);
state.registers.set_half_carry(false);
state.registers.set_carry(carry);
state.registers.opcode_bytecount = Some(2);
CycleResult::Finished
}
});
}
};
}
define_sra_reg!(0x28, b);
define_sra_reg!(0x29, c);
define_sra_reg!(0x2A, d);
define_sra_reg!(0x2B, e);
define_sra_reg!(0x2C, h);
define_sra_reg!(0x2D, l);
define_sra_reg!(0x2F, a);
macro_rules! define_swap_reg {
($op:literal, $reg:ident) => {
paste::paste! {

View file

@ -5,6 +5,7 @@ macro_rules! define_bitfield_u8_gs {
((self.$loc >> $offset) & 0b1) == 1
}
#[allow(unused)]
pub fn [<write_ $name>](&mut self, value: bool) {
log::debug!(std::concat!("Setting ", std::stringify!($name), " to {}"), value);
self.$loc &= !(0b1 << $offset);

View file

@ -66,6 +66,7 @@ impl MBC1 {
self.rom_bank_count > 4
}
#[allow(unused)]
fn is_large_ram(&self) -> bool {
self.ram_bank_count > 2
}
@ -98,16 +99,16 @@ impl Mapper for MBC1 {
}
}
fn read_eram_u8(&self, address: u16) -> u8 {
fn read_eram_u8(&self, _address: u16) -> u8 {
match self.ram.as_ref() {
Some(ram) => unimplemented!(),
Some(_ram) => unimplemented!(),
None => 0,
}
}
fn write_eram_u8(&mut self, address: u16, value: u8) {
fn write_eram_u8(&mut self, _address: u16, _value: u8) {
match self.ram.as_ref() {
Some(ram) => unimplemented!(),
Some(_ram) => unimplemented!(),
None => {}
}
}

View file

@ -120,6 +120,7 @@ impl OAMEntry {
(self.flags >> 5) & 0b1 == 1
}
#[allow(unused)]
pub fn palette_number(&self) -> bool {
(self.flags >> 4) & 0b1 == 1
}

View file

@ -25,6 +25,7 @@ impl Serial {
self.sc & 0b1 == 1
}
#[allow(unused)]
pub fn set_side(&mut self, conductor: bool) {
self.sc &= !0b1;
self.sc |= conductor as u8;
@ -35,7 +36,7 @@ impl Serial {
if self.internal_tick < 128 {
self.internal_tick += 1;
} else {
print!("{}", self.sb as char);
std::io::stdout().write_all(&[self.sb]).expect("writing stdout failed");
std::io::stdout().flush().expect("flushing stdout failed");
self.sb = 0;
self.set_transfer_in_process(false);

View file

@ -8,10 +8,11 @@ use std::{
};
use argh::FromArgs;
use chrono::Duration;
use chrono::{Duration, Utc};
use gameboy::Gameboy;
use settings::DeemgeeConfig;
use window::WindowEvent;
use sha1::{Digest, Sha1};
use window::EmulatorWindowEvent;
use crate::window::GameboyEvent;
@ -46,10 +47,13 @@ fn main() {
let args: CliArgs = argh::from_env();
let config = DeemgeeConfig::from_file();
let (window_side_tx, gb_side_rx) = channel::<WindowEvent>();
let (window_side_tx, gb_side_rx) = channel::<EmulatorWindowEvent>();
let (gb_side_tx, window_side_rx) = channel::<GameboyEvent>();
let jh = std::thread::spawn(move || run_gameboy(config, args, gb_side_rx, gb_side_tx).unwrap());
let jh = std::thread::Builder::new()
.name(String::from("mewmulator"))
.spawn(move || run_gameboy(config, args, gb_side_rx, gb_side_tx).unwrap())
.unwrap();
window::run_window(config, window_side_rx, window_side_tx);
@ -59,7 +63,7 @@ fn main() {
pub fn run_gameboy(
_config: DeemgeeConfig,
args: CliArgs,
rx: Receiver<WindowEvent>,
rx: Receiver<EmulatorWindowEvent>,
tx: Sender<GameboyEvent>,
) -> Result<(), DmgError> {
if !args.bootrom.is_file() {
@ -78,8 +82,12 @@ pub fn run_gameboy(
return Err(DmgError::BootromInvalidSize(bootrom.len() as u64));
}
if sha1::Sha1::from(bootrom.as_slice()).hexdigest().as_str()
!= "4ed31ec6b0b175bb109c0eb5fd3d193da823339f"
let mut hash_ctx = Sha1::new();
hash_ctx.update(&bootrom);
let digest = hash_ctx.finalize();
if digest.as_slice()
!= b"\x4e\xd3\x1e\xc6\xb0\xb1\x75\xbb\x10\x9c\x0e\xb5\xfd\x3d\x19\x3d\xa8\x23\x33\x9f"
{
return Err(DmgError::BootromInvalidHash);
}
@ -103,21 +111,35 @@ pub fn run_gameboy(
'outer: loop {
while let Ok(event) = rx.try_recv() {
match event {
window::WindowEvent::AToggle => gameboy.joypad.set_a(!gameboy.joypad.a),
window::WindowEvent::BToggle => gameboy.joypad.set_b(!gameboy.joypad.b),
window::WindowEvent::SelectToggle => {
window::EmulatorWindowEvent::AToggle => gameboy.joypad.set_a(!gameboy.joypad.a),
window::EmulatorWindowEvent::BToggle => gameboy.joypad.set_b(!gameboy.joypad.b),
window::EmulatorWindowEvent::SelectToggle => {
gameboy.joypad.set_select(!gameboy.joypad.select)
}
window::WindowEvent::StartToggle => gameboy.joypad.set_start(!gameboy.joypad.start),
window::WindowEvent::UpToggle => gameboy.joypad.set_up(!gameboy.joypad.up),
window::WindowEvent::DownToggle => gameboy.joypad.set_down(!gameboy.joypad.down),
window::WindowEvent::LeftToggle => gameboy.joypad.set_left(!gameboy.joypad.left),
window::WindowEvent::RightToggle => gameboy.joypad.set_right(!gameboy.joypad.right),
window::WindowEvent::PauseToggle => paused = !paused,
window::WindowEvent::LogToggle => {
window::EmulatorWindowEvent::StartToggle => {
gameboy.joypad.set_start(!gameboy.joypad.start)
}
window::EmulatorWindowEvent::UpToggle => gameboy.joypad.set_up(!gameboy.joypad.up),
window::EmulatorWindowEvent::DownToggle => {
gameboy.joypad.set_down(!gameboy.joypad.down)
}
window::EmulatorWindowEvent::LeftToggle => {
gameboy.joypad.set_left(!gameboy.joypad.left)
}
window::EmulatorWindowEvent::RightToggle => {
gameboy.joypad.set_right(!gameboy.joypad.right)
}
window::EmulatorWindowEvent::PauseToggle => paused = !paused,
window::EmulatorWindowEvent::LogToggle => {
gameboy.log_instructions = !gameboy.log_instructions
}
window::WindowEvent::Exit => break 'outer,
window::EmulatorWindowEvent::Exit => break 'outer,
window::EmulatorWindowEvent::DumpMemory => {
let timestamp = Utc::now().timestamp();
let contents = gameboy.dump_memory();
std::fs::write(format!("./memdump-{}.bin", timestamp), contents)
.expect("Failed to write memory dump");
}
}
}

View file

@ -1,4 +1,4 @@
use winit::event::VirtualKeyCode;
use winit::keyboard::KeyCode;
#[derive(Debug, serde::Deserialize, Clone, Copy)]
pub struct DeemgeeConfig {
@ -7,24 +7,27 @@ pub struct DeemgeeConfig {
impl DeemgeeConfig {
pub fn from_file() -> Self {
let mut settings = config::Config::default();
settings.merge(config::File::with_name("config")).unwrap();
settings.try_into().expect("Config Error")
config::Config::builder()
.add_source(config::File::with_name("config"))
.build()
.and_then(config::Config::try_deserialize)
.expect("config")
}
}
#[derive(Debug, serde::Deserialize, Clone, Copy)]
pub struct Bindings {
pub a: VirtualKeyCode,
pub b: VirtualKeyCode,
pub select: VirtualKeyCode,
pub start: VirtualKeyCode,
pub up: VirtualKeyCode,
pub down: VirtualKeyCode,
pub left: VirtualKeyCode,
pub right: VirtualKeyCode,
pub a: KeyCode,
pub b: KeyCode,
pub select: KeyCode,
pub start: KeyCode,
pub up: KeyCode,
pub down: KeyCode,
pub left: KeyCode,
pub right: KeyCode,
pub pause: VirtualKeyCode,
pub exit: VirtualKeyCode,
pub log_ops: VirtualKeyCode,
pub pause: KeyCode,
pub exit: KeyCode,
pub log_ops: KeyCode,
pub dump_memory: KeyCode,
}

View file

@ -2,8 +2,9 @@ use std::sync::mpsc::{Receiver, Sender};
use pixels::{Pixels, SurfaceTexture};
use winit::{
event::{Event, VirtualKeyCode},
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
keyboard::KeyCode,
window::WindowBuilder,
};
use winit_input_helper::WinitInputHelper;
@ -15,21 +16,21 @@ macro_rules! define_keypress {
if $input.key_pressed($config.bindings.$key)
&& !*$keymap.idx(&$config, $config.bindings.$key)
{
$tx.send(WindowEvent::$event).unwrap();
$tx.send(EmulatorWindowEvent::$event).unwrap();
*$keymap.idx(&$config, $config.bindings.$key) = true;
}
if $input.key_released($config.bindings.$key)
&& *$keymap.idx(&$config, $config.bindings.$key)
{
$tx.send(WindowEvent::$event).unwrap();
$tx.send(EmulatorWindowEvent::$event).unwrap();
*$keymap.idx(&$config, $config.bindings.$key) = false;
}
};
}
#[derive(Debug, Clone, Copy)]
pub enum WindowEvent {
pub enum EmulatorWindowEvent {
AToggle,
BToggle,
SelectToggle,
@ -41,6 +42,7 @@ pub enum WindowEvent {
PauseToggle,
LogToggle,
Exit,
DumpMemory,
}
#[derive(Debug)]
@ -65,7 +67,7 @@ struct Keymap {
}
impl Keymap {
pub fn idx(&mut self, config: &DeemgeeConfig, kc: VirtualKeyCode) -> &mut bool {
pub fn idx(&mut self, config: &DeemgeeConfig, kc: KeyCode) -> &mut bool {
if kc == config.bindings.a {
&mut self.a
} else if kc == config.bindings.b {
@ -90,8 +92,12 @@ impl Keymap {
}
}
pub fn run_window(config: DeemgeeConfig, rx: Receiver<GameboyEvent>, tx: Sender<WindowEvent>) {
let event_loop = EventLoop::new();
pub fn run_window(
config: DeemgeeConfig,
rx: Receiver<GameboyEvent>,
tx: Sender<EmulatorWindowEvent>,
) {
let event_loop = EventLoop::new().unwrap();
let mut input = WinitInputHelper::new();
let window = { WindowBuilder::new().with_title("OwO").build(&event_loop).unwrap() };
@ -107,70 +113,98 @@ pub fn run_window(config: DeemgeeConfig, rx: Receiver<GameboyEvent>, tx: Sender<
let mut keymap = Keymap::default();
event_loop.run(move |event, _, control_flow| {
if let Event::RedrawRequested(_) = event {
let frame = pixels.get_frame();
let mut request_redraw = false;
let mut close_requested = false;
match fb.as_ref() {
Some(fb) => {
redraw_happened = true;
frame.copy_from_slice(fb.as_slice());
}
None => {
let x = vec![0xff; frame.len()];
frame.copy_from_slice(x.as_slice())
}
}
if let Err(why) = pixels.render() {
log::error!("Pixels Error: {}", why);
*control_flow = ControlFlow::Exit;
tx.send(WindowEvent::Exit).unwrap();
return;
}
}
event_loop
.run(move |event, target| {
match &event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => close_requested = true,
WindowEvent::RedrawRequested => {
let frame = pixels.frame_mut();
if input.update(&event) {
if input.key_pressed(config.bindings.exit) || input.quit() {
*control_flow = ControlFlow::Exit;
tx.send(WindowEvent::Exit).unwrap();
return;
}
if input.key_pressed(config.bindings.pause) {
tx.send(WindowEvent::PauseToggle).unwrap();
}
if input.key_pressed(config.bindings.log_ops) {
tx.send(WindowEvent::LogToggle).unwrap();
}
define_keypress!(input, config, keymap, tx, a, AToggle);
define_keypress!(input, config, keymap, tx, b, BToggle);
define_keypress!(input, config, keymap, tx, start, StartToggle);
define_keypress!(input, config, keymap, tx, select, SelectToggle);
define_keypress!(input, config, keymap, tx, up, UpToggle);
define_keypress!(input, config, keymap, tx, down, DownToggle);
define_keypress!(input, config, keymap, tx, left, LeftToggle);
define_keypress!(input, config, keymap, tx, right, RightToggle);
}
if let Some(size) = input.window_resized() {
pixels.resize_surface(size.width, size.height);
window.request_redraw();
redraw_happened = false;
}
while let Ok(event) = rx.try_recv() {
match event {
GameboyEvent::Framebuffer(buf) => {
fb = Some(buf);
if redraw_happened {
match fb.as_ref() {
Some(fb) => {
redraw_happened = true;
frame.copy_from_slice(fb.as_slice());
}
None => {
let x = vec![0xff; frame.len()];
frame.copy_from_slice(x.as_slice())
}
}
if let Err(why) = pixels.render() {
log::error!("Pixels Error: {}", why);
close_requested = true;
tx.send(EmulatorWindowEvent::Exit).unwrap();
return;
}
}
_ => {}
},
Event::AboutToWait => {
if request_redraw && !close_requested {
window.request_redraw();
redraw_happened = false;
}
target.set_control_flow(ControlFlow::Poll);
if close_requested {
target.exit();
}
}
_ => {}
}
if input.update(&event) {
if input.key_pressed(config.bindings.exit)
|| (input.close_requested() || input.destroyed())
{
tx.send(EmulatorWindowEvent::Exit).unwrap();
return;
}
if input.key_pressed(config.bindings.pause) {
tx.send(EmulatorWindowEvent::PauseToggle).unwrap();
}
if input.key_pressed(config.bindings.log_ops) {
tx.send(EmulatorWindowEvent::LogToggle).unwrap();
}
if input.key_pressed(config.bindings.dump_memory) {
tx.send(EmulatorWindowEvent::DumpMemory).unwrap();
}
define_keypress!(input, config, keymap, tx, a, AToggle);
define_keypress!(input, config, keymap, tx, b, BToggle);
define_keypress!(input, config, keymap, tx, start, StartToggle);
define_keypress!(input, config, keymap, tx, select, SelectToggle);
define_keypress!(input, config, keymap, tx, up, UpToggle);
define_keypress!(input, config, keymap, tx, down, DownToggle);
define_keypress!(input, config, keymap, tx, left, LeftToggle);
define_keypress!(input, config, keymap, tx, right, RightToggle);
}
if let Some(size) = input.window_resized() {
pixels.resize_surface(size.width, size.height).unwrap();
window.request_redraw();
redraw_happened = false;
}
while let Ok(event) = rx.try_recv() {
match event {
GameboyEvent::Framebuffer(buf) => {
fb = Some(buf);
if redraw_happened {
request_redraw = true;
redraw_happened = false;
}
}
}
}
}
});
})
.expect("event loop error");
}

View file

@ -1 +1 @@
stable
nightly