use std::{cell::RefCell, fmt, rc::Rc};
use sdl3::EventPump;
use serde::{Deserialize, Serialize};
use crate::libs::{
memory::{Memory, MemoryData},
ports::Ports,
ppi::PPIData,
};
use super::{
api::{FLAG_C, FLAG_H, FLAG_N, FLAG_V},
hook::FuncHook,
instructions::execute_opcode,
opcodes_disassembler::disassemble_map,
opcodes_timings_msx::get_timings,
util::{join_bytes, sign_extend, split_word, tern_op_b},
z80_tables,
};
pub(crate) const SHIFT_0X_CB: u16 = 256;
pub(crate) const SHIFT_0X_ED: u16 = 512;
pub(crate) const SHIFT_0X_DD: u16 = 768;
pub(crate) const SHIFT_0X_DDCB: u16 = 1024;
pub(crate) const SHIFT_0X_FDCB: u16 = 1024;
pub(crate) const SHIFT_0X_FD: u16 = 1280;
pub struct Register16 {
high: u8,
low: u8,
}
impl Register16 {
pub(crate) fn new(high: u8, low: u8) -> Self {
Self { high, low }
}
pub(crate) fn result(&self) -> (u8, u8) {
(self.high, self.low)
}
pub(crate) fn set(&mut self, value: u16) {
(self.high, self.low) = split_word(value);
}
pub(crate) fn get(&mut self) -> u16 {
join_bytes(self.high, self.low)
}
}
#[derive(Serialize, Deserialize, Clone)]
#[allow(non_snake_case)]
pub struct Z80Data {
pub A: u8,
pub F: u8,
pub B: u8,
pub C: u8,
pub D: u8,
pub E: u8,
pub H: u8,
pub L: u8,
pub(crate) A_: u8,
pub(crate) F_: u8,
pub(crate) B_: u8,
pub(crate) C_: u8,
pub(crate) D_: u8,
pub(crate) E_: u8,
pub(crate) H_: u8,
pub(crate) L_: u8,
pub(crate) IXH: u8,
pub(crate) IXL: u8,
pub(crate) IYH: u8,
pub(crate) IYL: u8,
pub(crate) I: u8,
pub(crate) IFF1: u8,
pub(crate) IFF2: u8,
pub(crate) IM: u8,
pub(crate) R7: u8,
pub(crate) R: u16,
pub(crate) sp: u16,
pub(crate) pc: u16,
pub(crate) event_next_event: isize,
pub(crate) t_states: isize,
pub(crate) halted: bool,
pub(crate) temp_addr: u16,
pub(crate) interrupts_enabled_at: isize,
pub(crate) rzx_instructions_offset: isize,
pub cycles: u64,
pub(crate) debug: bool,
pub(crate) debug_step: usize,
}
impl fmt::Debug for Z80Data {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"[Z80Data]\n\
\tA :0x{:02x} F :0x{:02x} BC :0x{:04x} DE :0x{:04x} HL :0x{:04x}\n\
\tA_:0x{:02x} F_:0x{:02x} BC_:0x{:04x} DE_:0x{:04x} HL_:0x{:04x}\n\
\tIX:0x{:04x} IY:0x{:04x} I :0x{:02x} IF1:0x{:02x} IF2:0x{:02x}\n\
\tIM:0x{:02x} R7:0x{:02x} R :0x{:04x} sp :0x{:04x} pc :0x{:04x}\n\
\tene:0x{:04x} ts:0x{:04x} t.a:0x{:04x} iea:0x{:04x} rio:0x{:04x}\n",
self.A,
self.F,
self.bc(),
self.de(),
self.hl(),
self.A_,
self.F_,
self.bc_(),
self.de_(),
self.hl_(),
self.ix(),
self.iy(),
self.I,
self.IFF1,
self.IFF2,
self.IM,
self.R7,
self.R,
self.sp,
self.pc,
self.event_next_event,
self.t_states,
self.temp_addr,
self.interrupts_enabled_at,
self.rzx_instructions_offset,
)
}
}
macro_rules! fn_inc_reg {
($fn:tt, $r:ident) => {
pub fn $fn(&mut self) {
self.$r = self.$r.wrapping_add(1);
self.F = self.F & FLAG_C
| tern_op_b(self.$r == 0x80, FLAG_V, 0)
| tern_op_b((self.$r & 0x0f) != 0, 0, FLAG_H)
| z80_tables::get().sz53_table[self.$r as usize];
}
};
}
macro_rules! fn_dec_reg {
($fn:tt, $r:ident) => {
pub fn $fn(&mut self) {
self.F = self.F & FLAG_C | tern_op_b(self.$r & 0x0f != 0, 0, FLAG_H) | FLAG_N;
self.$r = self.$r.wrapping_sub(1);
self.F |= tern_op_b(self.$r == 0x7f, FLAG_V, 0)
| z80_tables::get().sz53_table[self.$r as usize];
}
};
}
macro_rules! fn_get_reg16 {
($fn:tt, $rh:ident, $rl:ident) => {
pub fn $fn(&self) -> u16 {
join_bytes(self.$rh, self.$rl)
}
};
}
macro_rules! fn_set_reg16 {
($fn:tt, $rh:ident, $rl:ident) => {
pub fn $fn(&mut self, value: u16) {
(self.$rh, self.$rl) = split_word(value);
}
};
}
macro_rules! fn_dec_reg16 {
($fn:tt, $r:ident, $fs:ident) => {
pub fn $fn(&mut self) {
let r = self.$r();
self.$fs(r.wrapping_sub(1));
}
};
}
macro_rules! fn_inc_reg16 {
($fn:tt, $r:ident, $fs:ident) => {
pub fn $fn(&mut self) {
let r = self.$r();
self.$fs(r.wrapping_add(1));
}
};
}
#[allow(dead_code)]
impl Z80Data {
pub(crate) fn new() -> Self {
Self {
A: 0,
F: 0,
B: 0,
C: 0,
D: 0,
E: 0,
H: 0,
L: 0,
A_: 0,
F_: 0,
B_: 0,
C_: 0,
D_: 0,
E_: 0,
H_: 0,
L_: 0,
IXH: 0,
IXL: 0,
IYH: 0,
IYL: 0,
I: 0,
IFF1: 0,
IFF2: 0,
IM: 0,
R7: 0,
R: 0,
sp: 0,
pc: 0,
event_next_event: 0,
t_states: 0,
halted: false,
temp_addr: 0,
interrupts_enabled_at: 0,
rzx_instructions_offset: 0,
cycles: 0,
debug: false,
debug_step: 0,
}
}
pub(crate) fn reset(&mut self) {
(
self.A, self.F, self.B, self.C, self.D, self.E, self.H, self.L,
) = (0, 0, 0, 0, 0, 0, 0, 0);
(
self.A_, self.F_, self.B_, self.C_, self.D_, self.E_, self.H_, self.L_,
) = (0, 0, 0, 0, 0, 0, 0, 0);
(self.IXH, self.IXL, self.IYH, self.IYL) = (0, 0, 0, 0);
(
self.sp, self.I, self.R, self.R7, self.pc, self.IFF1, self.IFF2, self.IM,
) = (0, 0, 0, 0, 0, 0, 0, 0);
self.t_states = 0;
self.halted = false;
self.interrupts_enabled_at = 0;
self.debug_step = 0;
}
pub fn set_debug(&mut self, debug: bool) {
self.debug = debug;
}
pub fn set_debug_step(&mut self, step: usize) {
self.debug_step = step;
}
pub fn get_debug_step(&self) -> usize {
self.debug_step
}
pub fn increase_debug_step(&mut self) {
self.debug_step += 1;
}
pub fn decrease_debug_step(&mut self) {
self.debug_step -= 1;
}
pub fn pc(&self) -> u16 {
self.pc
}
pub fn set_pc(&mut self, value: u16) {
self.pc = value;
}
pub fn inc_pc(&mut self, value: u16) {
self.pc += value;
}
pub(crate) fn dec_pc(&mut self, value: u16) {
self.pc -= value;
}
pub fn sp(&self) -> u16 {
self.sp
}
pub(crate) fn set_sp(&mut self, value: u16) {
self.sp = value;
}
pub(crate) fn inc_sp(&mut self) {
self.sp += 1;
}
pub(crate) fn dec_sp(&mut self) {
self.sp -= 1;
}
pub(crate) fn ir(&mut self) -> u16 {
let mut ir: u16 = 0;
ir |= (self.I as u16) << 8;
ir |= self.R7 as u16 & 0x80 | self.R & 0x7f;
ir
}
fn_inc_reg!(inc_a, A);
fn_dec_reg!(dec_a, A);
fn_inc_reg!(inc_b, B);
fn_dec_reg!(dec_b, B);
fn_inc_reg!(inc_c, C);
fn_dec_reg!(dec_c, C);
fn_inc_reg!(inc_d, D);
fn_dec_reg!(dec_d, D);
fn_inc_reg!(inc_e, E);
fn_dec_reg!(dec_e, E);
fn_inc_reg!(inc_f, F);
fn_dec_reg!(dec_f, F);
fn_inc_reg!(inc_h, H);
fn_dec_reg!(dec_h, H);
fn_inc_reg!(inc_i, I);
fn_dec_reg!(dec_i, I);
fn_inc_reg!(inc_l, L);
fn_dec_reg!(dec_l, L);
fn_inc_reg!(inc_r7, R7);
fn_dec_reg!(dec_r7, R7);
fn_inc_reg!(inc_a_, A_);
fn_dec_reg!(dec_a_, A_);
fn_inc_reg!(inc_b_, B_);
fn_dec_reg!(dec_b_, B_);
fn_inc_reg!(inc_c_, C_);
fn_dec_reg!(dec_c_, C_);
fn_inc_reg!(inc_d_, D_);
fn_dec_reg!(dec_d_, D_);
fn_inc_reg!(inc_e_, E_);
fn_dec_reg!(dec_e_, E_);
fn_inc_reg!(inc_f_, F_);
fn_dec_reg!(dec_f_, F_);
fn_inc_reg!(inc_h_, H_);
fn_dec_reg!(dec_h_, H_);
fn_inc_reg!(inc_l_, L_);
fn_dec_reg!(dec_l_, L_);
fn_inc_reg!(inc_ixl, IXL);
fn_dec_reg!(dec_ixl, IXL);
fn_inc_reg!(inc_ixh, IXH);
fn_dec_reg!(dec_ixh, IXH);
fn_inc_reg!(inc_iyl, IYL);
fn_dec_reg!(dec_iyl, IYL);
fn_inc_reg!(inc_iyh, IYH);
fn_dec_reg!(dec_iyh, IYH);
fn_get_reg16!(bc, B, C);
fn_set_reg16!(set_bc, B, C);
fn_dec_reg16!(dec_bc, bc, set_bc);
fn_inc_reg16!(inc_bc, bc, set_bc);
fn_get_reg16!(de, D, E);
fn_set_reg16!(set_de, D, E);
fn_dec_reg16!(dec_de, de, set_de);
fn_inc_reg16!(inc_de, de, set_de);
fn_get_reg16!(hl, H, L);
fn_set_reg16!(set_hl, H, L);
fn_dec_reg16!(dec_hl, hl, set_hl);
fn_inc_reg16!(inc_hl, hl, set_hl);
fn_get_reg16!(bc_, B_, C_);
fn_set_reg16!(set_bc_, B_, C_);
fn_dec_reg16!(dec_bc_, bc_, set_bc_);
fn_inc_reg16!(inc_bc_, bc_, set_bc_);
fn_get_reg16!(de_, D_, E_);
fn_set_reg16!(set_de_, D_, E_);
fn_dec_reg16!(dec_de_, de_, set_de_);
fn_inc_reg16!(inc_de_, de_, set_de_);
fn_get_reg16!(hl_, H_, L_);
fn_set_reg16!(set_hl_, H_, L_);
fn_dec_reg16!(dec_hl_, hl_, set_hl_);
fn_inc_reg16!(inc_hl_, hl_, set_hl_);
fn_get_reg16!(ix, IXH, IXL);
fn_set_reg16!(set_ix, IXH, IXL);
fn_dec_reg16!(dec_ix, ix, set_ix);
fn_inc_reg16!(inc_ix, ix, set_ix);
fn_get_reg16!(iy, IYH, IYL);
fn_set_reg16!(set_iy, IYH, IYL);
fn_dec_reg16!(dec_iy, iy, set_iy);
fn_inc_reg16!(inc_iy, iy, set_iy);
}
impl Default for Z80Data {
fn default() -> Self {
Self::new()
}
}
pub struct DefaultFuncHook {}
impl Default for DefaultFuncHook {
fn default() -> Self {
Self::new()
}
}
impl DefaultFuncHook {
pub(crate) fn new() -> Self {
Self {}
}
}
impl FuncHook for DefaultFuncHook {}
#[allow(non_snake_case)]
pub struct Z80<'a> {
pub(super) data: Z80Data,
pub(crate) memory: Memory,
ports: Ports<'a>,
pub(crate) hook: Rc<RefCell<dyn FuncHook>>,
}
#[allow(non_snake_case)]
impl<'a> Z80<'a> {
pub fn new(memory: Memory, ports: Ports<'a>) -> Self {
Self {
data: Z80Data::default(),
memory,
ports,
hook: Rc::new(RefCell::new(DefaultFuncHook::new())),
}
}
pub fn set_hook(&mut self, hook: Rc<RefCell<dyn FuncHook>>) {
self.hook = hook;
}
pub(crate) fn reset(&mut self) {
self.data.reset();
}
pub fn reset_cycles(&mut self) {
self.data.cycles = 0;
}
pub fn get_cycles(&self) -> u64 {
self.data.cycles
}
pub fn is_halted(&self) -> bool {
self.data.halted
}
fn normalize_cycles(&mut self, cyclesPerFrame: u64) {
self.data.cycles %= cyclesPerFrame;
}
fn can_run_within_frame(&self, cyclesPerFrame: u64) -> bool {
self.data.cycles < cyclesPerFrame
}
pub fn run_test(&mut self) {
while !self.is_halted() {
do_opcode(
&mut self.data,
&mut self.memory,
&mut self.ports,
self.hook.clone(),
)
}
}
pub(crate) fn interrupt(&mut self) {
if self.data.IFF1 != 0 {
if self.data.halted {
self.data.pc += 1;
self.data.halted = false;
}
self.data.t_states += 7;
self.data.R = (self.data.R + 1) & 0x7f;
(self.data.IFF1, self.data.IFF2) = (0, 0);
{
let (pch, pcl) = split_word(self.data.pc);
self.data.sp -= 1;
self.memory.write_byte(self.data.sp, pch);
self.data.sp -= 1;
self.memory.write_byte(self.data.sp, pcl);
}
match self.data.IM {
0 | 1 => {
self.data.pc = 0x0038;
}
2 => {
let mut int_temp: u16 = ((self.data.I as u16) << 8) | 0xff;
let pcl = self.memory.read_byte(int_temp);
int_temp += 1;
let pch = self.memory.read_byte(int_temp);
self.data.pc = join_bytes(pch, pcl);
}
_ => {
panic!("Unknown interrupt mode");
}
}
}
}
#[allow(dead_code)]
pub(crate) fn non_maskable_interrupt(&mut self) {
if self.data.halted {
self.data.pc += 1;
self.data.halted = false;
}
self.data.t_states += 7;
self.data.R = (self.data.R + 1) & 0x7f;
(self.data.IFF1, self.data.IFF2) = (0, 0);
{
let (pch, pcl) = split_word(self.data.pc);
self.data.sp -= 1;
self.memory.write_byte(self.data.sp, pch);
self.data.sp -= 1;
self.memory.write_byte(self.data.sp, pcl);
}
self.data.pc = 0x0066;
}
pub(crate) fn get_save_data(&self) -> (Z80Data, MemoryData, PPIData) {
(
self.data.clone(),
self.memory.get_data(),
self.memory.get_ppi_data(),
)
}
pub(crate) fn set_save_data(
&mut self,
data: Z80Data,
memoryData: MemoryData,
ppiData: PPIData,
) {
self.data = data;
self.memory.set_data(memoryData);
self.memory.set_ppi_data(ppiData);
}
pub fn reboot(&mut self) {
self.reset();
self.data.set_pc(0);
}
pub(crate) fn run_one_frame(&mut self, cyclesPerFrame: u64) {
self.normalize_cycles(cyclesPerFrame);
while self.can_run_within_frame(cyclesPerFrame) {
if self.is_halted() {
break;
}
do_opcode(
&mut self.data,
&mut self.memory,
&mut self.ports,
self.hook.clone(),
);
}
}
pub fn check_keycodes(&mut self, event_pump: &mut EventPump) {
self.ports.check_keycodes(event_pump);
}
}
fn do_opcode(
data: &mut Z80Data,
memory: &mut Memory,
ports: &mut Ports,
hook: Rc<RefCell<dyn FuncHook>>,
) {
if hook
.borrow_mut()
.handle_generic_hook(data, memory, ports)
.is_continue()
{
return;
}
memory.contend_read(data.pc, 4);
let opcode = memory.read_byte_internal(data.pc);
data.R = (data.R + 1) & 0x7f;
data.pc += 1;
data.cycles += get_timings(data, opcode as u16);
if data.debug {
disassemble_map(data, memory, opcode as u16);
}
match opcode {
0xcb => {
opcode_cb(data, memory, ports, hook);
}
0xdd => {
opcode_dd(data, memory, ports, hook);
}
0xed => {
opcode_ed(data, memory, ports, hook);
}
0xfd => {
opcode_fd(data, memory, ports, hook);
}
_ => {
execute_opcode(data, memory, ports, hook, opcode as u16);
}
}
}
fn opcode_cb(
data: &mut Z80Data,
memory: &mut Memory,
ports: &mut Ports,
hook: Rc<RefCell<dyn FuncHook>>,
) {
memory.contend_read(data.pc, 4);
let opcode2: u8 = memory.read_byte_internal(data.pc);
data.pc += 1;
data.R += 1;
execute_opcode(data, memory, ports, hook, SHIFT_0X_CB + opcode2 as u16);
data.cycles += get_timings(data, SHIFT_0X_CB + opcode2 as u16);
}
fn opcode_dd(
data: &mut Z80Data,
memory: &mut Memory,
ports: &mut Ports,
hook: Rc<RefCell<dyn FuncHook>>,
) {
memory.contend_read(data.pc, 4);
let opcode2: u8 = memory.read_byte_internal(data.pc);
data.pc += 1;
data.R += 1;
match opcode2 {
0xcb => {
memory.contend_read(data.pc, 3);
data.temp_addr = data.ix() + sign_extend(memory.read_byte_internal(data.pc)) as u16;
data.pc += 1;
memory.contend_read(data.pc, 3);
let opcode3: u8 = memory.read_byte_internal(data.pc);
memory.contend_read_no_mreq_loop(data.pc, 1, 2);
data.pc += 1;
execute_opcode(data, memory, ports, hook, SHIFT_0X_DDCB + (opcode3 as u16));
data.cycles += get_timings(data, SHIFT_0X_DDCB + opcode3 as u16);
}
_ => {
if execute_opcode(
data,
memory,
ports,
hook.clone(),
SHIFT_0X_DD + (opcode2 as u16),
) {
data.cycles += get_timings(data, SHIFT_0X_DD + (opcode2 as u16));
} else {
execute_opcode(data, memory, ports, hook, opcode2 as u16);
data.cycles += get_timings(data, opcode2 as u16);
}
}
}
}
fn opcode_ed(
data: &mut Z80Data,
memory: &mut Memory,
ports: &mut Ports,
hook: Rc<RefCell<dyn FuncHook>>,
) {
memory.contend_read(data.pc, 4);
let opcode2: u8 = memory.read_byte_internal(data.pc);
data.pc += 1;
data.R += 1;
data.cycles += get_timings(data, SHIFT_0X_ED + opcode2 as u16);
if !execute_opcode(
data,
memory,
ports,
hook.clone(),
SHIFT_0X_ED + opcode2 as u16,
) {
invalid_opcode();
}
}
fn opcode_fd(
data: &mut Z80Data,
memory: &mut Memory,
ports: &mut Ports,
hook: Rc<RefCell<dyn FuncHook>>,
) {
memory.contend_read(data.pc, 4);
let opcode2: u8 = memory.read_byte_internal(data.pc);
data.pc += 1;
data.R += 1;
match opcode2 {
0xcb => {
memory.contend_read(data.pc, 3);
data.temp_addr = data.iy() + sign_extend(memory.read_byte_internal(data.pc)) as u16;
data.pc += 1;
memory.contend_read(data.pc, 3);
let opcode3: u8 = memory.read_byte_internal(data.pc);
memory.contend_read_no_mreq_loop(data.pc, 1, 2);
data.pc += 1;
execute_opcode(data, memory, ports, hook, SHIFT_0X_FDCB + (opcode3 as u16));
data.cycles += get_timings(data, SHIFT_0X_FDCB + (opcode3 as u16));
}
_ => {
if execute_opcode(
data,
memory,
ports,
hook.clone(),
SHIFT_0X_FD + opcode2 as u16,
) {
data.cycles += get_timings(data, SHIFT_0X_FD + opcode2 as u16);
} else {
execute_opcode(data, memory, ports, hook, opcode2 as u16);
data.cycles += get_timings(data, opcode2 as u16);
}
}
}
}
fn invalid_opcode() {
panic!("invalid opcode");
}