olympia_engine 0.3.0

Olympia is a gameboy emulator and toolkit, intended to run as a native or web assembly application targeting a cycle count accurate emulation. olympia_engine is the reusable core for both native and wasm.
Documentation
use super::misc::{exec_rotate, SetZeroMode};
use crate::gameboy::{GameBoy, StepResult};
use crate::instructions::{Carry, RotateDirection};
use crate::instructionsn::{ExecutableInstruction, RuntimeOpcode};
use crate::registers::{ByteRegisterTarget, Flag};

use alloc::boxed::Box;
use alloc::vec::Vec;

use olympia_derive::OlympiaInstruction;

#[derive(Debug, OlympiaInstruction)]
#[olympia(opcode = 0x01_AAA_BBB, label = "BIT", extended)]
struct TestBit {
    #[olympia(dest, mask = 0xA)]
    bit: u8,
    #[olympia(src, mask = 0xB)]
    target: ByteRegisterTarget,
}

impl ExecutableInstruction for TestBit {
    fn execute(&self, gb: &mut GameBoy) -> StepResult<()> {
        let val = gb.exec_read_register_target(self.target)?;
        let bit_test = val & (1 << self.bit);
        gb.set_flag_to(Flag::Zero, bit_test == 0);
        gb.set_flag(Flag::HalfCarry);
        gb.set_flag_to(Flag::AddSubtract, false);
        Ok(())
    }
}

#[derive(Debug, OlympiaInstruction)]
#[olympia(opcode = 0x10_AAA_BBB, label = "RES", extended)]
struct ResetBit {
    #[olympia(dest, mask = 0xA)]
    bit: u8,
    #[olympia(src, mask = 0xB)]
    target: ByteRegisterTarget,
}

impl ExecutableInstruction for ResetBit {
    fn execute(&self, gb: &mut GameBoy) -> StepResult<()> {
        let val = gb.exec_read_register_target(self.target)?;
        let new_val = val & !(1 << self.bit);
        gb.exec_write_register_target(self.target, new_val)?;
        Ok(())
    }
}

#[derive(Debug, OlympiaInstruction)]
#[olympia(opcode = 0x11_AAA_BBB, label = "SET", extended)]
struct SetBit {
    #[olympia(dest, mask = 0xA)]
    bit: u8,
    #[olympia(src, mask = 0xB)]
    target: ByteRegisterTarget,
}

impl ExecutableInstruction for SetBit {
    fn execute(&self, gb: &mut GameBoy) -> StepResult<()> {
        let val = gb.exec_read_register_target(self.target)?;
        let new_val = val | (1 << self.bit);
        gb.exec_write_register_target(self.target, new_val)?;
        Ok(())
    }
}

macro_rules! ext_rotate_instruction {
    ($name:ident, $label:literal, $opcode:literal, $dir:path, $carry:path) => {
        #[derive(Debug, OlympiaInstruction)]
        #[olympia(opcode = $opcode, label = $label, extended)]
        struct $name {
            #[olympia(dest, mask = 0xA)]
            target: ByteRegisterTarget,
        }
        impl ExecutableInstruction for $name {
            fn execute(&self, gb: &mut GameBoy) -> StepResult<()> {
                exec_rotate(gb, $dir, $carry, self.target, SetZeroMode::Test)?;
                Ok(())
            }
        }
    };
}

ext_rotate_instruction!(
    RotateLeftCarry,
    "RLC",
    0x0000_0AAA,
    RotateDirection::Left,
    Carry::Carry
);
ext_rotate_instruction!(
    RotateLeft,
    "RL",
    0x0001_0AAA,
    RotateDirection::Left,
    Carry::NoCarry
);
ext_rotate_instruction!(
    RotateRightCarry,
    "RRC",
    0x0000_1AAA,
    RotateDirection::Right,
    Carry::Carry
);
ext_rotate_instruction!(
    RotateRight,
    "RR",
    0x0001_1AAA,
    RotateDirection::Right,
    Carry::NoCarry
);

fn exec_shift_zero(
    gb: &mut GameBoy,
    dir: RotateDirection,
    target: ByteRegisterTarget,
) -> StepResult<()> {
    let value = gb.exec_read_register_target(target)?;
    let (shifted_value, carry) = match dir {
        RotateDirection::Left => (value << 1, value & 0x80 != 0),
        RotateDirection::Right => (value >> 1, value & 0x01 != 0),
    };
    gb.set_flag_to(Flag::Carry, carry);
    gb.exec_write_register_target(target, shifted_value)?;
    Ok(())
}

#[derive(Debug, OlympiaInstruction)]
#[olympia(opcode = 0x0010_0AAA, label = "SLA", extended)]
struct ShiftLeftZero {
    #[olympia(single, mask = 0xA)]
    target: ByteRegisterTarget,
}

impl ExecutableInstruction for ShiftLeftZero {
    fn execute(&self, gb: &mut GameBoy) -> StepResult<()> {
        exec_shift_zero(gb, RotateDirection::Left, self.target)
    }
}

#[derive(Debug, OlympiaInstruction)]
#[olympia(opcode = 0x0011_1AAA, label = "SRL", extended)]
struct ShiftRightZero {
    #[olympia(single, mask = 0xA)]
    target: ByteRegisterTarget,
}

impl ExecutableInstruction for ShiftRightZero {
    fn execute(&self, gb: &mut GameBoy) -> StepResult<()> {
        exec_shift_zero(gb, RotateDirection::Right, self.target)
    }
}

#[derive(Debug, OlympiaInstruction)]
#[olympia(opcode = 0x0010_1AAA, label = "SRA", extended)]
struct ShiftRightExtend {
    #[olympia(single, mask = 0xA)]
    target: ByteRegisterTarget,
}

impl ExecutableInstruction for ShiftRightExtend {
    fn execute(&self, gb: &mut GameBoy) -> StepResult<()> {
        let value = gb.exec_read_register_target(self.target)?;
        let value16 = u16::from(value);
        let extra_bit = (value16 << 1) & 0xff00;
        let shifted_value = (extra_bit + value16) >> 1;
        let actual_byte = shifted_value.to_le_bytes()[0];
        gb.exec_write_register_target(self.target, actual_byte)?;
        Ok(())
    }
}

#[derive(Debug, OlympiaInstruction)]
#[olympia(opcode = 0x0011_0AAA, label = "SWAP", extended)]
struct Swap {
    #[olympia(dest, mask = 0xA)]
    target: ByteRegisterTarget,
}

impl ExecutableInstruction for Swap {
    fn execute(&self, gb: &mut GameBoy) -> StepResult<()> {
        let value = gb.exec_read_register_target(self.target)?;
        let low_nibble = value & 0x0F;
        let high_nibble = value & 0xF0;
        let new_value = (low_nibble.rotate_left(4)) + (high_nibble.rotate_right(4));
        gb.exec_write_register_target(self.target, new_value)?;
        Ok(())
    }
}

pub(crate) fn opcodes() -> Vec<(u8, Box<dyn RuntimeOpcode>)> {
    vec![
        TestBitOpcode::all(),
        ResetBitOpcode::all(),
        SetBitOpcode::all(),
        RotateLeftCarryOpcode::all(),
        RotateLeftOpcode::all(),
        RotateRightCarryOpcode::all(),
        RotateRightOpcode::all(),
        ShiftLeftZeroOpcode::all(),
        ShiftRightZeroOpcode::all(),
        ShiftRightExtendOpcode::all(),
        SwapOpcode::all(),
    ]
    .into_iter()
    .flatten()
    .collect()
}