multi: upgrade dependencies, fix a bug with interrupts, implement some missing opcodes
This commit is contained in:
parent
00850645cd
commit
9e199f8a44
18 changed files with 1523 additions and 945 deletions
1873
Cargo.lock
generated
1873
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -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"
|
|
@ -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"
|
||||
|
|
|
@ -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! {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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! {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 => {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -120,6 +120,7 @@ impl OAMEntry {
|
|||
(self.flags >> 5) & 0b1 == 1
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn palette_number(&self) -> bool {
|
||||
(self.flags >> 4) & 0b1 == 1
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
stable
|
||||
nightly
|
Loading…
Reference in a new issue