diff --git a/gb-emu/src/camera.rs b/gb-emu/src/camera.rs index 0176daa..5c7c46f 100644 --- a/gb-emu/src/camera.rs +++ b/gb-emu/src/camera.rs @@ -36,4 +36,12 @@ impl PocketCamera for Webcam { } buffer } + + fn begin_capture(&mut self) { + todo!() + } + + fn is_capturing(&self) -> bool { + todo!() + } } diff --git a/lib/src/connect/mod.rs b/lib/src/connect/mod.rs index 0d90a29..7eae20f 100644 --- a/lib/src/connect/mod.rs +++ b/lib/src/connect/mod.rs @@ -68,6 +68,10 @@ impl AudioOutput { pub trait PocketCamera { // resolution - 128x128 fn capture_greyscale(&mut self) -> [u8; 128 * 128]; + + fn begin_capture(&mut self); + + fn is_capturing(&self) -> bool; } pub struct NoCamera; @@ -76,6 +80,12 @@ impl PocketCamera for NoCamera { fn capture_greyscale(&mut self) -> [u8; 128 * 128] { [0; 128 * 128] } + + fn begin_capture(&mut self) {} + + fn is_capturing(&self) -> bool { + false + } } #[non_exhaustive] @@ -83,7 +93,7 @@ pub struct EmulatorOptions where ColourFormat: From + Clone, R: Renderer, - C: PocketCamera, + C: PocketCamera + Send + 'static, { pub(crate) window: R, pub(crate) tile_window: Option, @@ -124,7 +134,7 @@ impl EmulatorOptions where ColourFormat: From + Clone, R: Renderer, - C: PocketCamera, + C: PocketCamera + Send + 'static, { pub fn new_with_camera(window: R, rom: RomFile, output: AudioOutput, camera: C) -> Self { Self { diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 11fcd1f..fe0f077 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -2,7 +2,8 @@ use crate::{processor::memory::Memory, util::pause}; use connect::{ - AudioOutput, EmulatorMessage, EmulatorOptions, PocketCamera, Renderer, RomFile, SerialTarget, + AudioOutput, EmulatorMessage, EmulatorOptions, NoCamera, PocketCamera, Renderer, RomFile, + SerialTarget, }; use once_cell::sync::OnceCell; use processor::{ @@ -37,10 +38,10 @@ pub struct EmulatorCore where ColourFormat: From + Clone, R: Renderer, - C: PocketCamera, + C: PocketCamera + Send + 'static, { receiver: Receiver, - cpu: Cpu, + cpu: Cpu, spooky: PhantomData, } @@ -48,7 +49,7 @@ impl EmulatorCore where ColourFormat: From + Clone, R: Renderer, - C: PocketCamera, + C: PocketCamera + Send + 'static, { pub fn init( receiver: Receiver, @@ -71,14 +72,14 @@ where }; match fs::read(path) { - Ok(data) => Rom::load(data, maybe_save), + Ok(data) => Rom::load(data, maybe_save, options.camera), Err(e) => { println!("Error reading ROM: {e}"); exit(1); } } } - RomFile::Raw(data) => Rom::load(data, None), + RomFile::Raw(data) => Rom::load(data, None, options.camera), }; options.window.prepare(WIDTH, HEIGHT); @@ -114,7 +115,7 @@ where ) } - fn new(receiver: Receiver, cpu: Cpu) -> Self { + fn new(receiver: Receiver, cpu: Cpu) -> Self { Self { receiver, cpu, @@ -178,7 +179,13 @@ where { CpuSaveState::create(&self.cpu) } +} +impl EmulatorCore +where + ColourFormat: From + Clone, + R: Renderer, +{ pub fn from_save_state( state: CpuSaveState, rom: RomFile, @@ -199,7 +206,7 @@ where }; Self { receiver, - cpu: Cpu::from_save_state(state, data, window, output, serial_target), + cpu: Cpu::from_save_state(state, data, window, output, serial_target, NoCamera), spooky: PhantomData, } } diff --git a/lib/src/processor/instructions/instructions.rs b/lib/src/processor/instructions/instructions.rs index edbba15..d8a5c93 100644 --- a/lib/src/processor/instructions/instructions.rs +++ b/lib/src/processor/instructions/instructions.rs @@ -1,13 +1,14 @@ use crate::{ - connect::Renderer, + connect::{PocketCamera, Renderer}, processor::{memory::mmio::gpu::Colour, Cpu, Direction, Flags, Reg8, SplitRegister}, util::{clear_bit, get_bit, set_bit}, }; -impl Cpu +impl Cpu where ColourFormat: From + Clone, R: Renderer, + C: PocketCamera + Send + 'static, { pub(crate) fn and(&mut self, first: u8, second: u8) -> u8 { let result = first & second; diff --git a/lib/src/processor/instructions/primitives.rs b/lib/src/processor/instructions/primitives.rs index d3f2472..2de2f3e 100644 --- a/lib/src/processor/instructions/primitives.rs +++ b/lib/src/processor/instructions/primitives.rs @@ -1,14 +1,15 @@ use crate::{ - connect::Renderer, + connect::{PocketCamera, Renderer}, processor::{memory::mmio::gpu::Colour, Cpu, Direction, Flags, SplitRegister}, util::{as_signed, get_bit, get_rotation_carry, rotate, Nibbles}, }; use std::ops::{BitAnd, BitOr}; -impl Cpu +impl Cpu where ColourFormat: From + Clone, R: Renderer, + C: PocketCamera + Send + 'static, { pub(crate) fn pop_word(&mut self) -> u16 { let mut word: u16 = 0x0; diff --git a/lib/src/processor/memory.rs b/lib/src/processor/memory.rs index c56b540..6cfc032 100644 --- a/lib/src/processor/memory.rs +++ b/lib/src/processor/memory.rs @@ -9,7 +9,7 @@ use self::{ rom::RomSaveState, }; use crate::{ - connect::{AudioOutput, JoypadState, Renderer, SerialTarget}, + connect::{AudioOutput, JoypadState, PocketCamera, Renderer, SerialTarget}, processor::SplitRegister, verbose_println, Cpu, }; @@ -22,13 +22,14 @@ pub(crate) mod rom; pub(crate) type Address = u16; -pub struct Memory +pub struct Memory where ColourFormat: From + Clone, R: Renderer, + C: PocketCamera + Send + 'static, { bootrom: Option>, - rom: Rom, + rom: Rom, ram: [u8; 8192], cpu_ram: [u8; 128], pub(super) interrupts: Interrupts, @@ -69,8 +70,9 @@ impl MemorySaveState where ColourFormat: From + Clone, R: Renderer, + // C: PocketCamera + Send + 'static, { - pub fn create(memory: &Memory) -> Self { + pub fn create(memory: &Memory) -> Self { Self { rom: RomSaveState::create(&memory.rom), ram: memory.ram, @@ -88,14 +90,15 @@ where } } -impl Memory +impl Memory where ColourFormat: From + Clone, R: Renderer, + C: PocketCamera + Send + 'static, { pub fn init( bootrom: Option>, - rom: Rom, + rom: Rom, window: R, output: AudioOutput, serial_target: SerialTarget, @@ -295,10 +298,11 @@ where window: R, output: AudioOutput, serial_target: SerialTarget, + camera: C, ) -> Self { Self { bootrom: None, - rom: Rom::from_save_state(state.rom, data), + rom: Rom::from_save_state(state.rom, data, camera), ram: state.ram, cpu_ram: state.cpu_ram, interrupts: state.interrupts, @@ -314,10 +318,11 @@ where } } -impl Cpu +impl Cpu where ColourFormat: From + Clone, R: Renderer, + C: PocketCamera + Send + 'static, { pub fn increment_timers(&mut self, machine_cycles: u8) { let steps = (machine_cycles as usize) * 4; diff --git a/lib/src/processor/memory/rom.rs b/lib/src/processor/memory/rom.rs index 503e1ae..da44fba 100644 --- a/lib/src/processor/memory/rom.rs +++ b/lib/src/processor/memory/rom.rs @@ -1,15 +1,17 @@ use serde::{Deserialize, Serialize}; -use crate::processor::memory::Address; +use crate::{connect::PocketCamera as PocketCameraTrait, processor::memory::Address}; use std::{ fs::{File, OpenOptions}, io::{Read, Seek, SeekFrom, Write}, + marker::PhantomData, path::PathBuf, str::from_utf8_unchecked, }; use self::mbcs::{ Mbc, Mbc1, Mbc1SaveState, Mbc2, Mbc2SaveState, Mbc3, Mbc3SaveState, Mbc5, Mbc5SaveState, None, + PocketCamera, PocketCameraSaveState, }; mod mbcs; @@ -120,9 +122,13 @@ impl Drop for MaybeBufferedSram { } } -pub struct Rom { +pub struct Rom +where + C: PocketCameraTrait, +{ title: String, mbc: Box, + spooky: PhantomData, } #[derive(Serialize, Deserialize)] @@ -132,7 +138,7 @@ pub struct RomSaveState { } impl RomSaveState { - pub fn create(rom: &Rom) -> Self { + pub fn create(rom: &Rom) -> Self { Self { title: rom.title.clone(), mbc: rom.mbc.get_save_state(), @@ -146,23 +152,34 @@ enum MbcSaveState { Mbc2(Mbc2SaveState), Mbc3(Mbc3SaveState), Mbc5(Mbc5SaveState), + PocketCamera(PocketCameraSaveState), None, } impl MbcSaveState { - fn get_mbc(self, data: Vec) -> Box { + fn get_mbc( + self, + data: Vec, + camera: C, + ) -> Box { match self { MbcSaveState::Mbc1(state) => Box::new(Mbc1::from_save_state(state, data)), MbcSaveState::Mbc2(state) => Box::new(Mbc2::from_save_state(state, data)), MbcSaveState::Mbc3(state) => Box::new(Mbc3::from_save_state(state, data)), MbcSaveState::Mbc5(state) => Box::new(Mbc5::from_save_state(state, data)), MbcSaveState::None => Box::new(None::init(data)), + MbcSaveState::PocketCamera(state) => { + Box::new(PocketCamera::from_save_state(state, data, camera)) + } } } } -impl Rom { - pub fn load(data: Vec, save_path: Option) -> Self { +impl Rom +where + C: PocketCameraTrait + Send + 'static, +{ + pub fn load(data: Vec, save_path: Option, camera: C) -> Self { let mut title_length = 0x143; for (i, val) in data.iter().enumerate().take(0x143).skip(0x134) { title_length = i; @@ -195,15 +212,23 @@ impl Rom { 0x1C => Box::new(Mbc5::init(data, rom_size, 0, true, None)), 0x1D => Box::new(Mbc5::init(data, rom_size, ram_size, true, None)), 0x1E => Box::new(Mbc5::init(data, rom_size, ram_size, true, save_path)), + 0xFC => Box::new(PocketCamera::init( + data, rom_size, ram_size, save_path, camera, + )), _ => panic!("unimplemented mbc: {:#X}", data[0x147]), }; - Self { title, mbc } + Self { + title, + mbc, + spooky: PhantomData, + } } - pub fn from_save_state(state: RomSaveState, data: Vec) -> Self { + pub fn from_save_state(state: RomSaveState, data: Vec, camera: C) -> Self { Self { title: state.title, - mbc: state.mbc.get_mbc(data), + mbc: state.mbc.get_mbc(data, camera), + spooky: PhantomData, } } diff --git a/lib/src/processor/memory/rom/mbcs.rs b/lib/src/processor/memory/rom/mbcs.rs index 3ab5c67..60cf897 100644 --- a/lib/src/processor/memory/rom/mbcs.rs +++ b/lib/src/processor/memory/rom/mbcs.rs @@ -5,11 +5,13 @@ mod mbc2; mod mbc3; mod mbc5; mod none; +mod pocketcamera; pub use mbc1::{Mbc1, Mbc1SaveState}; pub use mbc2::{Mbc2, Mbc2SaveState}; pub use mbc3::{Mbc3, Mbc3SaveState}; pub use mbc5::{Mbc5, Mbc5SaveState}; pub use none::None; +pub use pocketcamera::{PocketCamera, PocketCameraSaveState}; use super::MbcSaveState; diff --git a/lib/src/processor/memory/rom/mbcs/pocketcamera.rs b/lib/src/processor/memory/rom/mbcs/pocketcamera.rs new file mode 100644 index 0000000..e9f63f9 --- /dev/null +++ b/lib/src/processor/memory/rom/mbcs/pocketcamera.rs @@ -0,0 +1,181 @@ +use super::{ram_size_kb, rom_banks, Mbc, KB, RAM_BANK_SIZE, ROM_BANK_SIZE}; +use crate::{ + connect::PocketCamera as PocketCameraTrait, + processor::memory::{ + rom::{MaybeBufferedSram, MbcSaveState}, + Address, + }, +}; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; + +enum RamBank { + Ram(u8), + Camera, +} + +pub struct PocketCamera +where + C: PocketCameraTrait, +{ + data: Vec, + rom_bank: u8, + rom_size: usize, + ram: Option, + ram_bank: RamBank, + ram_size: usize, + ram_enabled: bool, + camera: C, + extra_bits_a000: u8, + camera_ram: [u8; 52], +} + +#[derive(Serialize, Deserialize)] +pub struct PocketCameraSaveState; + +impl PocketCamera +where + C: PocketCameraTrait, +{ + pub fn init( + data: Vec, + rom_size: u8, + ram_size: u8, + save_file: Option, + camera: C, + ) -> Self { + let ram = ram_size_kb(ram_size).map(|s| MaybeBufferedSram::new(save_file, s * KB)); + Self { + data, + rom_bank: 1, + rom_size: rom_banks(rom_size) * ROM_BANK_SIZE, + ram, + ram_bank: RamBank::Ram(0), + ram_size: ram_size_kb(ram_size).map_or(1, |s| s * KB), + ram_enabled: false, + camera, + extra_bits_a000: 0, + camera_ram: [0; 52], + } + } + + fn get_rom_addr(&self, address: Address) -> usize { + (match address { + 0x0..0x4000 => address as usize, + 0x4000..0x8000 => { + let internal_addr = address as usize - 0x4000; + internal_addr + (ROM_BANK_SIZE * self.rom_bank as usize) + } + _ => panic!("address {address} incompatible with MBC"), + } % self.rom_size) + } + + fn get_ram_addr(&self, address: Address, bank: u8) -> usize { + ((address as usize - 0xA000) + (RAM_BANK_SIZE * bank as usize)) % self.ram_size + } + + pub fn from_save_state(_state: PocketCameraSaveState, _data: Vec, _camera: C) -> Self { + todo!(); + } + + fn get_cam_reg(&self, address: Address) -> u8 { + match address { + 0xA000 => (if self.camera.is_capturing() { 0x1 } else { 0x0 }) | self.extra_bits_a000, + _ => 0x00, + } + } + + fn set_cam_reg(&mut self, address: Address, data: u8) { + match address { + 0xA000 => { + if data & 0x1 == 0x1 { + self.camera.begin_capture(); + } + self.extra_bits_a000 = data & 0b110; + } + _ => {} + } + } +} + +impl Mbc for PocketCamera +where + C: PocketCameraTrait + Send, +{ + fn get(&self, address: Address) -> u8 { + self.data[self.get_rom_addr(address)] + } + + fn get_ram(&self, address: Address) -> u8 { + match self.ram_bank { + RamBank::Ram(bank) => { + if let Some(ram) = &self.ram { + ram.get(self.get_ram_addr(address, bank)) + } else { + 0xFF + } + } + RamBank::Camera => self.get_cam_reg(address), + } + } + + fn set(&mut self, address: Address, data: u8) { + match address { + 0x0..0x2000 => { + if (data & 0xF) == 0xA { + self.ram_enabled = true + } else { + self.ram_enabled = false + } + } + 0x2000..0x4000 => { + if data < 0x40 { + self.rom_bank = data + } + } + 0x4000..0x6000 => { + self.ram_bank = if data & 0xF0 != 0 { + RamBank::Camera + } else { + RamBank::Ram(data) + } + } + 0x6000..0x8000 => {} + _ => panic!("address {address} incompatible with MBC"), + } + } + + fn set_ram(&mut self, address: Address, data: u8) { + match self.ram_bank { + RamBank::Ram(bank) => { + let real_addr = self.get_ram_addr(address, bank); + if self.ram_enabled && let Some(ram) = &mut self.ram { + ram.set(real_addr, data); + } + } + RamBank::Camera => self.set_cam_reg(address, data), + } + } + + fn mbc_type(&self) -> String { + if let Some(ram) = &self.ram { + format!( + "{}KB Pocket Camera with {}KB RAM", + self.rom_size / KB, + ram.len() / KB, + ) + } else { + format!("{}KB Pocket Camera", self.rom_size / KB) + } + } + + fn flush(&mut self) { + if let Some(ref mut ram) = self.ram { + ram.flush(); + } + } + + fn get_save_state(&self) -> MbcSaveState { + MbcSaveState::PocketCamera(PocketCameraSaveState) + } +} diff --git a/lib/src/processor/mod.rs b/lib/src/processor/mod.rs index 2a40404..c91bafc 100644 --- a/lib/src/processor/mod.rs +++ b/lib/src/processor/mod.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; use self::memory::{mmio::gpu::Colour, Interrupt, Memory, MemorySaveState}; use crate::{ - connect::{AudioOutput, Renderer, SerialTarget}, + connect::{AudioOutput, PocketCamera, Renderer, SerialTarget}, verbose_println, }; @@ -23,12 +23,13 @@ pub(crate) enum Direction { Right, } -pub struct Cpu +pub struct Cpu where ColourFormat: From + Clone, R: Renderer, + C: PocketCamera + Send + 'static, { - pub memory: Memory, + pub memory: Memory, pub reg: Registers, pub last_instruction: u8, last_instruction_addr: u16, @@ -55,7 +56,7 @@ where ColourFormat: From + Clone, R: Renderer, { - pub fn create(cpu: &Cpu) -> Self { + pub fn create(cpu: &Cpu) -> Self { Self { memory: MemorySaveState::create(&cpu.memory), reg: cpu.reg, @@ -67,12 +68,13 @@ where } } -impl Cpu +impl Cpu where ColourFormat: From + Clone, R: Renderer, + C: PocketCamera + Send + 'static, { - pub fn new(mut memory: Memory, run_bootrom: bool) -> Self { + pub fn new(mut memory: Memory, run_bootrom: bool) -> Self { if !run_bootrom { memory.cpu_ram_init(); } @@ -176,9 +178,17 @@ where window: R, output: AudioOutput, serial_target: SerialTarget, + camera: C, ) -> Self { Self { - memory: Memory::from_save_state(state.memory, data, window, output, serial_target), + memory: Memory::from_save_state( + state.memory, + data, + window, + output, + serial_target, + camera, + ), reg: state.reg, last_instruction: state.last_instruction, last_instruction_addr: state.last_instruction_addr, diff --git a/lib/src/processor/opcodes.rs b/lib/src/processor/opcodes.rs index 42e8d02..8574a9f 100644 --- a/lib/src/processor/opcodes.rs +++ b/lib/src/processor/opcodes.rs @@ -1,5 +1,5 @@ use crate::{ - connect::Renderer, + connect::{PocketCamera, Renderer}, processor::{ instructions::instructions::{res, set}, Cpu, Flags, Reg8, SplitRegister, @@ -9,10 +9,11 @@ use crate::{ use super::memory::mmio::gpu::Colour; -impl Cpu +impl Cpu where ColourFormat: From + Clone, R: Renderer, + C: PocketCamera + Send + 'static, { pub fn run_opcode(&mut self, opcode: u8) -> u8 { match opcode {