Compare commits

...

53 Commits

Author SHA1 Message Date
6c1ad38f0a Fix sound once again :) 2021-09-30 12:33:14 +02:00
cd2f9ddfd8 Reduce noise by wram bank switching 2020-02-21 18:51:33 +01:00
65e65be0eb Trying to implement some y-mirroring 2020-02-21 18:50:57 +01:00
52c73538cb Fix x/y coordinate swap in sprites 2020-02-21 18:42:07 +01:00
515321110b gbc: Implement background priority bit 2020-02-21 18:39:17 +01:00
1890dd2d3a Remove single-instruction helper func 2020-02-21 18:38:59 +01:00
1cc839c911 Move Sprite/BgAttribs to separate file 2020-02-21 18:25:11 +01:00
fbfe751d75 Cleanup some old DMG code 2020-02-21 18:18:33 +01:00
c880766ae4 Display: Splitting file up, staring with palette 2020-02-21 18:15:47 +01:00
8dd14c7719 Origin cleanup 2020-02-21 10:19:21 +01:00
30efdc1108 DMG: Create some abstraction 2020-02-21 09:55:28 +01:00
b7c1d9b8a0 ppu: Fix window 2020-02-21 00:15:09 +01:00
bdf51448f7 Do not fatal error out on y-mirroring 2020-02-21 00:12:40 +01:00
078da395ed Fix color bug
Oh man, that was way harder to track down that I want to admit.
Couple of hours wasted, yeeeah.
2020-02-20 23:41:11 +01:00
195d94ddb0 audio: improve it by essentially switching to i8 2020-02-20 21:19:14 +01:00
cd48f4c4c8 Make palette OOBs not fatal 2020-02-20 20:20:55 +01:00
2fb40e7b10 even more complains 2020-02-20 20:18:54 +01:00
f07a0032eb Remove old unnecessary cmp hack 2020-02-20 20:13:36 +01:00
4cf6225133 Address some of clippys concerns 2020-02-20 20:11:02 +01:00
ab7ee0988f Cleanup key handling 2020-02-20 20:00:13 +01:00
3b28268279 Add nixos dev config 2020-02-20 19:57:03 +01:00
76059f1e85 Let the compiler decide what to inline 2020-02-20 19:52:26 +01:00
26f28cdb48 Use TryFrom() instead of custom solution 2020-02-20 19:50:07 +01:00
0f20b2801b Ongoing cleanup 2020-02-20 19:31:41 +01:00
847d12c3a8 Cleanup MBC related code 2020-02-20 19:28:20 +01:00
fc935ab214 Small clippy improvements 2020-02-20 19:19:37 +01:00
fff0f05b73 Reduce old GB palette to two 2020-02-20 19:15:36 +01:00
6efb1507ff Fix warnings 2020-02-19 12:45:47 +01:00
60d098e83c cargo fix 2020-02-18 16:29:59 +01:00
aa329237ba cargo fmt 2020-02-18 16:29:29 +01:00
55373a6948 Improve DMA & MBC5 2020-02-18 16:29:05 +01:00
2bbcba2152 Fix sprite color & wide sprites 2020-02-18 16:26:09 +01:00
261fa4d78a GBC: Fix vram selection 2020-02-18 14:35:11 +01:00
79e31318e0 GBC: Increase supported boot rom length 2020-02-18 13:03:44 +01:00
368150e4c3 Check cartridge ram size upon load 2020-02-18 12:15:41 +01:00
0816f74528 Add write support for workram-copy 2020-02-18 10:40:30 +01:00
90baf0af78 MBC1: Allow writing to RAM 2020-02-18 10:35:41 +01:00
67e2d7140b WIP commit 2020-02-17 19:23:27 +01:00
92855bf6b9 Implement proper shutdown 2020-02-17 19:23:16 +01:00
74234dd405 GBC features 2020-02-16 20:53:34 +01:00
2f03b015f1 Small performance improvements 2020-02-16 20:53:25 +01:00
fa2fa9e5f5 Fix graphic bug 2020-02-14 22:26:05 +01:00
d1b089556e Sound improvements 2020-02-14 21:03:36 +01:00
28d792e48d Simplify framerate limit 2020-02-14 20:08:01 +01:00
eb62d59817 Add support for MBC5 2020-02-14 13:15:52 +01:00
99dc3210d6 more code fixes 2020-02-14 11:45:37 +01:00
93e38d49ef cargo fmt 2020-02-14 11:39:20 +01:00
57b8438144 Merge branch 'master' into audio 2020-02-14 11:39:01 +01:00
Kevin Hamacher
2367e891a4 As good as working 2017-01-19 22:34:15 +01:00
Kevin Hamacher
fc15b398ba Channel 1 and 2 almost work correctly; Channel 3 is still annoying 2017-01-19 00:00:31 +01:00
Kevin Hamacher
9ddd8ac21c Trying to figure this audio stuff out 2017-01-17 17:56:11 +01:00
Kevin Hamacher
807d65ca53 Merge branch 'master' of ssh://gitlab.0x90.cc:2222/chief/rustyboy 2017-01-13 22:52:23 +01:00
Kevin Hamacher
59e6e956b0 Update readme 2016-06-02 21:33:19 +02:00
25 changed files with 1930 additions and 705 deletions

1
.envrc Normal file
View File

@ -0,0 +1 @@
use_nix

View File

@ -4,6 +4,11 @@ version = "0.1.0"
authors = ["Kevin Hamacher <kevin.hamacher@rub.de>"] authors = ["Kevin Hamacher <kevin.hamacher@rub.de>"]
edition = '2018' edition = '2018'
[features]
default = ["audio"]
audio = ["pulse-simple"]
[dependencies] [dependencies]
sdl2 = "*" sdl2 = "*"
libc = "*" libc = "*"
pulse-simple = { version = "*", optional = true }

View File

@ -4,10 +4,15 @@ Awesome GB(C?) emulator.
## Current status ## Current status
All instructions are completely implemented. Pretty much all instructions are implemented.
Display is able to render tiles + sprites, 8x16 sprites are implemented but untested. Display is able to render the background, the window and sprites.
Sound isn't implemented at all. 8x16 sprites are implemented but probably wrong.
## Test suite Sound is completely missing as well was most of the GBC features.
All CPU tests pass :) PKMN red works fine (except for minor graphic bugs).
Tetris runs correctly.
## Test suites
CPU test suite passes.

24
shell.nix Normal file
View File

@ -0,0 +1,24 @@
let
moz_overlay = import (builtins.fetchTarball https://github.com/mozilla/nixpkgs-mozilla/archive/master.tar.gz);
nixpkgs = import <nixpkgs> { overlays = [ moz_overlay ]; };
rustStableChannel = nixpkgs.latest.rustChannels.stable.rust.override {
extensions = [
"rust-src"
"rls-preview"
"clippy-preview"
"rustfmt-preview"
];
};
in
with nixpkgs;
stdenv.mkDerivation {
name = "moz_overlay_shell";
buildInputs = [
rustStableChannel
rls
rustup
pkg-config
SDL2
pulseaudio
];
}

View File

@ -1,4 +1,4 @@
use super::mbc::mbc::MBC; use crate::mbc;
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
enum MemoryBankControllerType { enum MemoryBankControllerType {
@ -6,6 +6,7 @@ enum MemoryBankControllerType {
MBC1, MBC1,
MBC2, MBC2,
MBC3, MBC3,
MBC5,
//HuC1, //HuC1,
} }
@ -18,17 +19,18 @@ enum RamSize {
} }
pub struct Cartridge { pub struct Cartridge {
mbc: Box<dyn super::mbc::mbc::MBC>, mbc: Box<dyn mbc::MBC>,
savefile: Option<String>, savefile: Option<String>,
} }
impl Cartridge { impl Cartridge {
pub fn new(rom: Box<[u8]>, save_file: Option<String>) -> Cartridge { pub fn new(rom: Box<[u8]>, savefile: Option<String>) -> Cartridge {
let mbc_type: MemoryBankControllerType = match rom[0x0147] { let mbc_type: MemoryBankControllerType = match rom[0x0147] {
0x00 | 0x08..=0x09 => MemoryBankControllerType::None, 0x00 | 0x08..=0x09 => MemoryBankControllerType::None,
0x01..=0x03 => MemoryBankControllerType::MBC1, 0x01..=0x03 => MemoryBankControllerType::MBC1,
0x05..=0x06 => MemoryBankControllerType::MBC2, 0x05..=0x06 => MemoryBankControllerType::MBC2,
0x0F..=0x13 => MemoryBankControllerType::MBC3, 0x0F..=0x13 => MemoryBankControllerType::MBC3,
0x19..=0x1E => MemoryBankControllerType::MBC5,
// 0xFF => MemoryBankControllerType::HuC1, // 0xFF => MemoryBankControllerType::HuC1,
_ => panic!("Unsupported MBC type: {:02X}", rom[0x0147]), _ => panic!("Unsupported MBC type: {:02X}", rom[0x0147]),
}; };
@ -55,23 +57,20 @@ impl Cartridge {
println!("Rom size: {} banks", rom_banks); println!("Rom size: {} banks", rom_banks);
println!("Ram size: {:?}", ram_size); println!("Ram size: {:?}", ram_size);
let ram = Cartridge::load_savefile(&save_file, ram_size); let ram = Cartridge::load_savefile(&savefile, ram_size);
let mbc: Box<dyn super::mbc::mbc::MBC> = match mbc_type { let mbc: Box<dyn mbc::MBC> = match mbc_type {
MemoryBankControllerType::None => Box::new(super::mbc::mbc::NoMBC::new(rom, ram)), MemoryBankControllerType::None => Box::new(mbc::NoMBC::new(rom, ram)),
MemoryBankControllerType::MBC1 => Box::new(super::mbc::mbc1::MBC1::new(rom, ram)), MemoryBankControllerType::MBC1 => Box::new(mbc::MBC1::new(rom, ram)),
MemoryBankControllerType::MBC2 => Box::new(super::mbc::mbc2::MBC2::new(rom, ram)), MemoryBankControllerType::MBC2 => Box::new(mbc::MBC2::new(rom, ram)),
MemoryBankControllerType::MBC3 => Box::new(super::mbc::mbc3::MBC3::new(rom, ram)), MemoryBankControllerType::MBC3 => Box::new(mbc::MBC3::new(rom, ram)),
MemoryBankControllerType::MBC5 => Box::new(mbc::MBC5::new(rom, ram)),
}; };
Cartridge { Cartridge { mbc, savefile }
mbc: mbc,
savefile: save_file,
}
} }
fn load_savefile(save_file: &Option<String>, ram_size: RamSize) -> Box<[u8]> { fn load_savefile(savefile: &Option<String>, ram_size: RamSize) -> Box<[u8]> {
let old_data;
let size = match ram_size { let size = match ram_size {
RamSize::None => 0, RamSize::None => 0,
RamSize::Ram2KB => 2048, RamSize::Ram2KB => 2048,
@ -79,24 +78,27 @@ impl Cartridge {
RamSize::Ram32KB => 16 * 2048, RamSize::Ram32KB => 16 * 2048,
}; };
if let &Some(ref filename) = save_file { if let Some(ref filename) = *savefile {
let data = super::read_file(&filename); let data = super::read_file(&filename);
if let Ok(success) = data { if let Ok(data) = data {
old_data = success if data.len() != size {
panic!("Ram size does not match");
}
data
} else { } else {
old_data = vec![0u8; size].into_boxed_slice(); // Generate empty buffer
println!("Warning: Could not open save file");
vec![0u8; size].into_boxed_slice()
} }
} else { } else {
old_data = vec![0u8; size].into_boxed_slice(); vec![0u8; size].into_boxed_slice()
} }
old_data
} }
pub fn save(&self) { pub fn save(&self) {
if let &Some(ref fil) = &self.savefile { if let Some(ref fil) = self.savefile {
println!("Updating RAM dump");
self.mbc.dump_ram(fil); self.mbc.dump_ram(fil);
println!("Ram dump updated.")
} else { } else {
println!("Did not update ram dump"); println!("Did not update ram dump");
} }

View File

@ -1,5 +1,6 @@
use super::interconnect; use crate::interconnect;
use std::convert::TryFrom;
use std::thread::sleep; use std::thread::sleep;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
@ -14,13 +15,19 @@ const REG_N_L: usize = 5;
const REG_N_HL: usize = 6; const REG_N_HL: usize = 6;
const REG_N_A: usize = 7; const REG_N_A: usize = 7;
const REG_N_F: usize = 8; const REG_N_F: usize = 8;
const REG_NAMES: [&'static str; 9] = ["B", "C", "D", "E", "H", "L", "(HL)", "A", "F"]; const REG_NAMES: [&str; 9] = ["B", "C", "D", "E", "H", "L", "(HL)", "A", "F"];
const FLAG_Z: u8 = 1 << 7; const FLAG_Z: u8 = 1 << 7;
const FLAG_N: u8 = 1 << 6; const FLAG_N: u8 = 1 << 6;
const FLAG_H: u8 = 1 << 5; const FLAG_H: u8 = 1 << 5;
const FLAG_C: u8 = 1 << 4; const FLAG_C: u8 = 1 << 4;
use interconnect::TickResult;
pub enum CpuTickResult {
Continue { cycles_executed: usize },
Shutdown { cycles_executed: usize },
}
pub struct CPU { pub struct CPU {
// Registers: B, C, D, E, H, L, A // Registers: B, C, D, E, H, L, A
regs: [u8; 7], regs: [u8; 7],
@ -34,10 +41,35 @@ pub struct CPU {
debug: bool, debug: bool,
halted: bool, halted: bool,
trigger_once: bool,
} }
fn to_u16(bytes: Box<[u8]>) -> u16 { enum Args {
(bytes[1] as u16) << 8 | (bytes[0] as u16) Single(u8),
Double(u8, u8),
}
impl TryFrom<Args> for u8 {
type Error = ();
fn try_from(val: Args) -> Result<u8, Self::Error> {
match val {
Args::Single(x) => Ok(x),
_ => Err(()),
}
}
}
impl TryFrom<Args> for u16 {
type Error = ();
fn try_from(val: Args) -> Result<u16, Self::Error> {
match val {
Args::Double(a, b) => Ok((a as u16) | ((b as u16) << 8)),
_ => Err(()),
}
}
} }
impl CPU { impl CPU {
@ -47,29 +79,37 @@ impl CPU {
regs: [0, 0, 0, 0, 0, 0, 0], regs: [0, 0, 0, 0, 0, 0, 0],
ip: 0, ip: 0,
sp: 0xFFFE, sp: 0xFFFE,
interconnect: interconnect, interconnect,
ime: false, ime: false,
debug: false, debug: false,
halted: false, halted: false,
trigger_once: false,
} }
} }
#[inline]
fn read_byte(&self, addr: u16) -> u8 { fn read_byte(&self, addr: u16) -> u8 {
self.interconnect.read_byte(addr) self.interconnect.read_byte(addr)
} }
#[inline] fn load_args(&mut self, num_args: u8) -> Args {
fn load_args(&mut self, num_args: u8) -> Box<[u8]> { match num_args {
let mut args = Vec::new(); 1 => {
for i in 0..num_args { let val = self.read_byte(self.ip);
args.push(self.read_byte(self.ip + i as u16)); self.ip += 1;
Args::Single(val)
}
2 => {
let b1 = self.read_byte(self.ip);
self.ip += 1;
let b2 = self.read_byte(self.ip);
self.ip += 1;
Args::Double(b1, b2)
}
_ => panic!("load_args only supports two bytes"),
} }
self.ip += num_args as u16;
args.into_boxed_slice()
} }
#[inline]
fn set_8bit_reg(&mut self, reg_id: usize, value: u8) { fn set_8bit_reg(&mut self, reg_id: usize, value: u8) {
// Make sure that we skip the (HL) part. // Make sure that we skip the (HL) part.
if reg_id == REG_N_A { if reg_id == REG_N_A {
@ -84,7 +124,6 @@ impl CPU {
} }
} }
#[inline]
fn get_8bit_reg(&self, reg_id: usize) -> u8 { fn get_8bit_reg(&self, reg_id: usize) -> u8 {
// Make sure that we skip the (HL) part. // Make sure that we skip the (HL) part.
if reg_id == REG_N_A { if reg_id == REG_N_A {
@ -99,17 +138,10 @@ impl CPU {
} }
} }
#[inline]
fn adc_r(&mut self, val: u8) { fn adc_r(&mut self, val: u8) {
// Passes the test
let old: u8 = self.regs[REG_A]; let old: u8 = self.regs[REG_A];
let mut new: u8 = old; let mut new: u8 = old;
let c: u8; let c = if self.flags & FLAG_C == FLAG_C { 1 } else { 0 };
if self.flags & FLAG_C == FLAG_C {
c = 1;
} else {
c = 0;
}
new = new.wrapping_add(val); new = new.wrapping_add(val);
new = new.wrapping_add(c); new = new.wrapping_add(c);
self.regs[REG_A] = new; self.regs[REG_A] = new;
@ -120,9 +152,7 @@ impl CPU {
self.set_clear_flag(FLAG_H, ((old & 0x0F) + (val & 0x0F) + c) > 0x0F); self.set_clear_flag(FLAG_H, ((old & 0x0F) + (val & 0x0F) + c) > 0x0F);
} }
#[inline]
fn add_r(&mut self, val: u8) { fn add_r(&mut self, val: u8) {
// Passes the test
let old: u8 = self.regs[REG_A]; let old: u8 = self.regs[REG_A];
let new: u8 = old.wrapping_add(val); let new: u8 = old.wrapping_add(val);
let carry: u16 = (old as u16) ^ (val as u16) ^ (new as u16); let carry: u16 = (old as u16) ^ (val as u16) ^ (new as u16);
@ -134,17 +164,10 @@ impl CPU {
self.set_clear_flag(FLAG_H, carry & 0x10 == 0x10); self.set_clear_flag(FLAG_H, carry & 0x10 == 0x10);
} }
#[inline]
fn sbc_r(&mut self, val: u8) { fn sbc_r(&mut self, val: u8) {
// Passes the test
let old: u8 = self.regs[REG_A]; let old: u8 = self.regs[REG_A];
let mut new: u8 = old as u8; let mut new: u8 = old as u8;
let c: u8; let c = if self.flags & FLAG_C == FLAG_C { 1 } else { 0 };
if self.flags & FLAG_C == FLAG_C {
c = 1;
} else {
c = 0;
}
new = new.wrapping_sub(val); new = new.wrapping_sub(val);
new = new.wrapping_sub(c); new = new.wrapping_sub(c);
@ -159,9 +182,7 @@ impl CPU {
); );
} }
#[inline]
fn sub_r(&mut self, val: u8) { fn sub_r(&mut self, val: u8) {
// Passes the test
let old: u8 = self.regs[REG_A]; let old: u8 = self.regs[REG_A];
let new: u8 = old.wrapping_sub(val); let new: u8 = old.wrapping_sub(val);
let carry: u16 = (old as u16) ^ (val as u16) ^ (new as u16); let carry: u16 = (old as u16) ^ (val as u16) ^ (new as u16);
@ -173,9 +194,7 @@ impl CPU {
self.set_clear_flag(FLAG_H, carry & 0x10 == 0x10); self.set_clear_flag(FLAG_H, carry & 0x10 == 0x10);
} }
#[inline]
fn cp_r(&mut self, val: u8) { fn cp_r(&mut self, val: u8) {
// Passes the test
let old: u8 = self.regs[REG_A]; let old: u8 = self.regs[REG_A];
let new: u8 = old.wrapping_sub(val); let new: u8 = old.wrapping_sub(val);
let carry: u16 = (old as u16) ^ (val as u16) ^ (new as u16); let carry: u16 = (old as u16) ^ (val as u16) ^ (new as u16);
@ -186,7 +205,6 @@ impl CPU {
self.set_clear_flag(FLAG_H, carry & 0x10 == 0x10); self.set_clear_flag(FLAG_H, carry & 0x10 == 0x10);
} }
#[inline]
fn run_prefix_instruction(&mut self) { fn run_prefix_instruction(&mut self) {
let instruction = self.read_byte(self.ip); let instruction = self.read_byte(self.ip);
self.ip += 1; self.ip += 1;
@ -199,12 +217,11 @@ impl CPU {
println!("RLC {}", REG_NAMES[reg_id]); println!("RLC {}", REG_NAMES[reg_id]);
} }
let nval: u8; let nval = if val & 0x80 == 0x80 {
if val & 0x80 == 0x80 { val << 1 | 1
nval = val << 1 | 1;
} else { } else {
nval = val << 1; val << 1
} };
self.set_8bit_reg(reg_id, nval); self.set_8bit_reg(reg_id, nval);
self.set_clear_flag(FLAG_C, val & 0x80 == 0x80); self.set_clear_flag(FLAG_C, val & 0x80 == 0x80);
@ -238,12 +255,7 @@ impl CPU {
} }
let carry = self.flags & FLAG_C > 0; let carry = self.flags & FLAG_C > 0;
let nval: u8; let nval = if !carry { val << 1 } else { val << 1 | 1 };
if !carry {
nval = val << 1;
} else {
nval = val << 1 | 1;
}
self.set_8bit_reg(reg_id, nval); self.set_8bit_reg(reg_id, nval);
self.set_clear_flag(FLAG_C, val & 0x80 == 0x80); self.set_clear_flag(FLAG_C, val & 0x80 == 0x80);
self.set_clear_flag(FLAG_Z, nval == 0); self.set_clear_flag(FLAG_Z, nval == 0);
@ -259,12 +271,7 @@ impl CPU {
} }
let carry = self.flags & FLAG_C > 0; let carry = self.flags & FLAG_C > 0;
let v: u8; let v = if !carry { val >> 1 } else { (val >> 1) | 0x80 };
if !carry {
v = val >> 1;
} else {
v = (val >> 1) | 0x80;
}
self.set_8bit_reg(reg_id, v); self.set_8bit_reg(reg_id, v);
self.set_clear_flag(FLAG_C, val & 1 == 1); self.set_clear_flag(FLAG_C, val & 1 == 1);
self.set_clear_flag(FLAG_Z, v == 0); self.set_clear_flag(FLAG_Z, v == 0);
@ -554,24 +561,18 @@ impl CPU {
} }
self.set_8bit_reg(reg_id, reg_content | (1 << 7)); self.set_8bit_reg(reg_id, reg_content | (1 << 7));
} }
_ => {
panic!("Unsupported prefix instruction: {:x}", instruction);
}
} }
} }
#[inline]
fn get_pair_value(&self, a: usize, b: usize) -> u16 { fn get_pair_value(&self, a: usize, b: usize) -> u16 {
(self.get_8bit_reg(a) as u16) << 8 | self.get_8bit_reg(b) as u16 (self.get_8bit_reg(a) as u16) << 8 | self.get_8bit_reg(b) as u16
} }
#[inline]
fn set_pair_value(&mut self, a: usize, b: usize, value: u16) { fn set_pair_value(&mut self, a: usize, b: usize, value: u16) {
self.set_8bit_reg(a, (value >> 8) as u8); self.set_8bit_reg(a, (value >> 8) as u8);
self.set_8bit_reg(b, value as u8); self.set_8bit_reg(b, value as u8);
} }
#[inline]
fn call(&mut self, dst: u16) { fn call(&mut self, dst: u16) {
let ip = self.ip; let ip = self.ip;
self.push(ip); self.push(ip);
@ -579,9 +580,8 @@ impl CPU {
self.ip = dst; self.ip = dst;
} }
#[inline] fn call_condition(&mut self, cond_str: &'static str, cond: bool) -> u8 {
fn call_condition(&mut self, cond_str: String, cond: bool) -> u8 { let dst = u16::try_from(self.load_args(2)).unwrap();
let dst = to_u16(self.load_args(2));
if self.debug { if self.debug {
println!("CALL {} {:04X}", cond_str, dst); println!("CALL {} {:04X}", cond_str, dst);
} }
@ -593,8 +593,7 @@ impl CPU {
} }
} }
#[inline] fn ret_condition(&mut self, cond_str: &'static str, cond: bool) -> u8 {
fn ret_condition(&mut self, cond_str: String, cond: bool) -> u8 {
if self.debug { if self.debug {
println!("RET {}", cond_str); println!("RET {}", cond_str);
} }
@ -606,7 +605,6 @@ impl CPU {
} }
} }
#[inline]
fn jmp_r(&mut self, addr: u8) { fn jmp_r(&mut self, addr: u8) {
let off: i8 = addr as i8; let off: i8 = addr as i8;
if off < 0 { if off < 0 {
@ -616,9 +614,8 @@ impl CPU {
} }
} }
#[inline] fn jmp_r_condition(&mut self, cond_str: &'static str, cond: bool) -> u8 {
fn jmp_r_condition(&mut self, cond_str: String, cond: bool) -> u8 { let t = u8::try_from(self.load_args(1)).unwrap();
let t = self.load_args(1)[0];
if self.debug { if self.debug {
println!("JR {} {:02X}", cond_str, t); println!("JR {} {:02X}", cond_str, t);
} }
@ -630,14 +627,12 @@ impl CPU {
} }
} }
#[inline]
fn jmp_p(&mut self, addr: u16) { fn jmp_p(&mut self, addr: u16) {
self.ip = addr; self.ip = addr;
} }
#[inline] fn jmp_p_condition(&mut self, cond_str: &'static str, cond: bool) -> u8 {
fn jmp_p_condition(&mut self, cond_str: String, cond: bool) -> u8 { let t = u16::try_from(self.load_args(2)).unwrap();
let t = to_u16(self.load_args(2));
if self.debug { if self.debug {
println!("JP {} {:04X}", cond_str, t); println!("JP {} {:04X}", cond_str, t);
} }
@ -649,7 +644,6 @@ impl CPU {
} }
} }
#[inline]
fn rst(&mut self, val: u8) -> u8 { fn rst(&mut self, val: u8) -> u8 {
// Make sure this is correct. // Make sure this is correct.
if self.debug { if self.debug {
@ -659,7 +653,6 @@ impl CPU {
16 16
} }
#[inline]
fn pop_rr(&mut self, r1: usize, r2: usize) -> u8 { fn pop_rr(&mut self, r1: usize, r2: usize) -> u8 {
if self.debug { if self.debug {
println!("POP {}{}", REG_NAMES[r1], REG_NAMES[r2]); println!("POP {}{}", REG_NAMES[r1], REG_NAMES[r2]);
@ -669,7 +662,6 @@ impl CPU {
12 12
} }
#[inline]
fn push_rr(&mut self, r1: usize, r2: usize) -> u8 { fn push_rr(&mut self, r1: usize, r2: usize) -> u8 {
if self.debug { if self.debug {
println!("PUSH {}{}", REG_NAMES[r1], REG_NAMES[r2]); println!("PUSH {}{}", REG_NAMES[r1], REG_NAMES[r2]);
@ -679,7 +671,6 @@ impl CPU {
16 16
} }
#[inline]
fn dec_rr(&mut self, r1: usize, r2: usize) -> u8 { fn dec_rr(&mut self, r1: usize, r2: usize) -> u8 {
if self.debug { if self.debug {
println!("DEC {}{}", REG_NAMES[r1], REG_NAMES[r2]); println!("DEC {}{}", REG_NAMES[r1], REG_NAMES[r2]);
@ -690,7 +681,6 @@ impl CPU {
8 8
} }
#[inline]
fn inc_rr(&mut self, r1: usize, r2: usize) -> u8 { fn inc_rr(&mut self, r1: usize, r2: usize) -> u8 {
if self.debug { if self.debug {
println!("INC {}{}", REG_NAMES[r1], REG_NAMES[r2]); println!("INC {}{}", REG_NAMES[r1], REG_NAMES[r2]);
@ -701,20 +691,17 @@ impl CPU {
8 8
} }
#[inline]
fn push(&mut self, val: u16) { fn push(&mut self, val: u16) {
self.interconnect.write_word(self.sp - 2, val); self.interconnect.write_word(self.sp - 2, val);
self.sp -= 2; self.sp -= 2;
} }
#[inline]
fn pop(&mut self) -> u16 { fn pop(&mut self) -> u16 {
let v: u16 = self.interconnect.read_word(self.sp); let v: u16 = self.interconnect.read_word(self.sp);
self.sp += 2; self.sp += 2;
v v
} }
#[inline]
fn ld_r_r(&mut self, reg_dst: usize, reg_src: usize) -> u8 { fn ld_r_r(&mut self, reg_dst: usize, reg_src: usize) -> u8 {
if self.debug { if self.debug {
println!("LD {}, {}", REG_NAMES[reg_dst], REG_NAMES[reg_src]) println!("LD {}, {}", REG_NAMES[reg_dst], REG_NAMES[reg_src])
@ -728,9 +715,8 @@ impl CPU {
} }
} }
#[inline]
fn ld_r_v(&mut self, r: usize) -> u8 { fn ld_r_v(&mut self, r: usize) -> u8 {
let val: u8 = self.load_args(1)[0]; let val = u8::try_from(self.load_args(1)).unwrap();
if self.debug { if self.debug {
println!("LD {}, {:02X}", REG_NAMES[r], val); println!("LD {}, {:02X}", REG_NAMES[r], val);
} }
@ -742,9 +728,8 @@ impl CPU {
} }
} }
#[inline]
fn ld_rr_vv(&mut self, r1: usize, r2: usize) -> u8 { fn ld_rr_vv(&mut self, r1: usize, r2: usize) -> u8 {
let val: u16 = to_u16(self.load_args(2)); let val = u16::try_from(self.load_args(2)).unwrap();
if self.debug { if self.debug {
println!("LD {}{}, {:04X}", REG_NAMES[r1], REG_NAMES[r2], val); println!("LD {}{}, {:04X}", REG_NAMES[r1], REG_NAMES[r2], val);
} }
@ -752,7 +737,6 @@ impl CPU {
12 12
} }
#[inline]
fn reg_inc(&mut self, reg_id: usize) -> u8 { fn reg_inc(&mut self, reg_id: usize) -> u8 {
if self.debug { if self.debug {
println!("INC {}", REG_NAMES[reg_id]); println!("INC {}", REG_NAMES[reg_id]);
@ -766,7 +750,6 @@ impl CPU {
4 4
} }
#[inline]
fn add_rr_rr(&mut self, r1: usize, r2: usize, r3: usize, r4: usize) -> u8 { fn add_rr_rr(&mut self, r1: usize, r2: usize, r3: usize, r4: usize) -> u8 {
if self.debug { if self.debug {
println!( println!(
@ -784,12 +767,11 @@ impl CPU {
self.clear_flag(FLAG_N); self.clear_flag(FLAG_N);
// Some magic formula // Some magic formula
self.set_clear_flag(FLAG_C, val1 as usize + val2 as usize & 0x10000 == 0x10000); self.set_clear_flag(FLAG_C, (val1 as usize + val2 as usize) & 0x10000 == 0x10000);
self.set_clear_flag(FLAG_H, (val1 ^ val2 ^ res) & 0x1000 == 0x1000); self.set_clear_flag(FLAG_H, (val1 ^ val2 ^ res) & 0x1000 == 0x1000);
8 8
} }
#[inline]
fn reg_dec(&mut self, reg_id: usize) -> u8 { fn reg_dec(&mut self, reg_id: usize) -> u8 {
if self.debug { if self.debug {
println!("DEC {}", REG_NAMES[reg_id]); println!("DEC {}", REG_NAMES[reg_id]);
@ -805,7 +787,6 @@ impl CPU {
4 4
} }
#[inline]
fn ld_dref_rr_a(&mut self, r1: usize, r2: usize) -> u8 { fn ld_dref_rr_a(&mut self, r1: usize, r2: usize) -> u8 {
if self.debug { if self.debug {
println!("LD ({}{}), A", REG_NAMES[r1], REG_NAMES[r2]); println!("LD ({}{}), A", REG_NAMES[r1], REG_NAMES[r2]);
@ -816,17 +797,14 @@ impl CPU {
8 8
} }
#[inline]
fn set_flag(&mut self, flag: u8) { fn set_flag(&mut self, flag: u8) {
self.flags |= flag; self.flags |= flag;
} }
#[inline]
fn clear_flag(&mut self, flag: u8) { fn clear_flag(&mut self, flag: u8) {
self.flags &= !flag; self.flags &= !flag;
} }
#[inline]
fn set_clear_flag(&mut self, flag: u8, dep: bool) { fn set_clear_flag(&mut self, flag: u8, dep: bool) {
if dep { if dep {
self.set_flag(flag); self.set_flag(flag);
@ -835,7 +813,6 @@ impl CPU {
} }
} }
#[inline]
fn int_(&mut self, val: u8) { fn int_(&mut self, val: u8) {
if self.debug { if self.debug {
println!("INT {:02X}", val); println!("INT {:02X}", val);
@ -845,21 +822,18 @@ impl CPU {
self.call(val as u16); self.call(val as u16);
} }
#[inline]
fn ret(&mut self) { fn ret(&mut self) {
let new_ip: u16 = self.pop(); let new_ip: u16 = self.pop();
//self.dump_stack(); //self.dump_stack();
self.ip = new_ip; self.ip = new_ip;
} }
#[inline]
fn reti(&mut self) -> u8 { fn reti(&mut self) -> u8 {
self.ret(); self.ret();
self.ime = true; self.ime = true;
16 16
} }
#[inline]
fn handle_interrupt(&mut self, offset: u8, flag: u8) { fn handle_interrupt(&mut self, offset: u8, flag: u8) {
// Remove interrupt requested flag // Remove interrupt requested flag
let new_flag = self.interconnect.read_byte(0xFF0F) & !flag; let new_flag = self.interconnect.read_byte(0xFF0F) & !flag;
@ -870,23 +844,27 @@ impl CPU {
self.halted = false; self.halted = false;
} }
#[allow(unused_variables)]
pub fn run(&mut self) { pub fn run(&mut self) {
let start = Instant::now();
let mut cycles_executed = 0u128;
loop { loop {
let mut cycles: i32 = 0; // Calculate the amount of cycles we should've executed by now.
let start = Instant::now(); // The gameboy has a freq of 4.194304MHz
for _ in 0..1000 { // This means it takes it 238.418569ns to execute a single
cycles += self.run_instruction() as i32; // cycle.
} let expected_cycles = start.elapsed().as_nanos() / (238 / 2);
while cycles_executed < expected_cycles {
let gb_dur = cycles * 238; if let CpuTickResult::Continue {
let our_dur = start.elapsed().subsec_nanos() as i32; cycles_executed: x, ..
let delta = gb_dur - our_dur; } = self.run_instruction()
if delta > (20 * 238) { {
// We're at least 20 cycles faster. cycles_executed += x as u128;
sleep(Duration::new(0, delta as u32)); } else {
} else if delta < 0 { return;
print!("-"); }
} }
sleep(Duration::new(0, 10 * 238));
} }
} }
@ -896,6 +874,8 @@ impl CPU {
let enabled = self.interconnect.read_byte(0xFFFF); let enabled = self.interconnect.read_byte(0xFFFF);
let e_pending = pending & enabled; let e_pending = pending & enabled;
// Handling interrupts. Do only execute the one with the highest
// priority
if e_pending & interconnect::INTERRUPT_DISPLAY_VBLANK > 0 { if e_pending & interconnect::INTERRUPT_DISPLAY_VBLANK > 0 {
if execute { if execute {
self.handle_interrupt(0x40, interconnect::INTERRUPT_DISPLAY_VBLANK); self.handle_interrupt(0x40, interconnect::INTERRUPT_DISPLAY_VBLANK);
@ -927,31 +907,58 @@ impl CPU {
false false
} }
pub fn run_instruction(&mut self) -> u8 { pub fn run_instruction(&mut self) -> CpuTickResult {
// self.debug = !self.interconnect.is_boot_rom(); // self.debug = !self.interconnect.is_boot_rom();
if !self.trigger_once && !self.interconnect.is_boot_rom() {
self.trigger_once = true;
self.regs[REG_A] = 0x11;
println!("Patching reg");
}
/*
if self.ip >= 100 && self.ip < 120 {
self.debug = true;
} else {
self.debug = false;
}
*/
// self.debug = true;
// Check for interrupts. // Check for interrupts.
if self.ime { if self.ime {
self.check_interrupts(true); self.check_interrupts(true);
} else if self.halted { } else if self.halted && self.check_interrupts(false) {
if self.check_interrupts(false) { self.halted = false;
self.halted = false;
}
} }
let mut cycles: u8 = 1; let mut cycles: u8 = 255;
let instruction: u8; let instruction: u8;
if !self.halted { if !self.halted {
// We need to double-check the flags // We need to double-check the flags
instruction = self.read_byte(self.ip); instruction = self.read_byte(self.ip);
if self.debug { if self.debug {
print!( print!(
"{:#06x}: [SP: {:#04X}] i={:02X}. ", "{:#06X}: [SP: {:#04X}] i={:02X}. ",
&self.ip, &self.sp, &instruction &self.ip, &self.sp, &instruction
); );
for i in 0..6 { /*
print!("{}: {:02X} ", REG_NAMES[i], self.get_8bit_reg(i)); for (idx, reg) in REG_NAMES.iter().enumerate() {
print!("{}: {:02X} ", reg, self.get_8bit_reg(idx));
} }
print!("A: {:02X} ", self.regs[REG_A]); print!("A: {:02X} ", self.regs[REG_A]);
*/
print!(
"AF={:02X}{:02X} BC={:02X}{:02X} DE={:02X}{:02X} HL={:02X}{:02X} ",
self.get_8bit_reg(REG_N_A),
self.get_8bit_reg(REG_N_F),
self.get_8bit_reg(REG_N_B),
self.get_8bit_reg(REG_N_C),
self.get_8bit_reg(REG_N_D),
self.get_8bit_reg(REG_N_E),
self.get_8bit_reg(REG_N_H),
self.get_8bit_reg(REG_N_L)
);
print!("I: {:02X} ", self.interconnect.read_byte(0xFFFF)); print!("I: {:02X} ", self.interconnect.read_byte(0xFFFF));
// Flags // Flags
@ -984,9 +991,10 @@ impl CPU {
let carry = val & 0x80 == 0x80; let carry = val & 0x80 == 0x80;
self.set_clear_flag(FLAG_C, carry); self.set_clear_flag(FLAG_C, carry);
if !carry { if !carry {
self.regs[REG_A] = self.regs[REG_A] << 1; self.regs[REG_A] <<= 1;
} else { } else {
self.regs[REG_A] = self.regs[REG_A] << 1 | 1; self.regs[REG_A] <<= 1;
self.regs[REG_A] |= 1;
} }
self.clear_flag(FLAG_Z); self.clear_flag(FLAG_Z);
self.clear_flag(FLAG_N); self.clear_flag(FLAG_N);
@ -994,7 +1002,7 @@ impl CPU {
4 4
} }
0x08 => { 0x08 => {
let a: u16 = to_u16(self.load_args(2)); let a = u16::try_from(self.load_args(2)).unwrap();
if self.debug { if self.debug {
println!("LD ({:04X}), sp", a); println!("LD ({:04X}), sp", a);
} }
@ -1022,9 +1030,10 @@ impl CPU {
let val = self.regs[REG_A]; let val = self.regs[REG_A];
self.set_clear_flag(FLAG_C, val & 1 == 1); self.set_clear_flag(FLAG_C, val & 1 == 1);
if val & 1 == 0 { if val & 1 == 0 {
self.regs[REG_A] = self.regs[REG_A] >> 1; self.regs[REG_A] >>= 1;
} else { } else {
self.regs[REG_A] = self.regs[REG_A] >> 1 | 0x80; self.regs[REG_A] >>= 1;
self.regs[REG_A] |= 0x80;
} }
self.clear_flag(FLAG_Z); self.clear_flag(FLAG_Z);
self.clear_flag(FLAG_N); self.clear_flag(FLAG_N);
@ -1033,7 +1042,10 @@ impl CPU {
} }
0x10 => { 0x10 => {
println!("STOP 0 {:02X} not implemented.", self.load_args(1)[0]); println!(
"STOP 0 {:02X} not implemented.",
u8::try_from(self.load_args(1)).unwrap()
);
4 4
} }
0x11 => self.ld_rr_vv(REG_N_D, REG_N_E), 0x11 => self.ld_rr_vv(REG_N_D, REG_N_E),
@ -1050,9 +1062,10 @@ impl CPU {
let val = self.regs[REG_A]; let val = self.regs[REG_A];
self.set_clear_flag(FLAG_C, val & 0x80 == 0x80); self.set_clear_flag(FLAG_C, val & 0x80 == 0x80);
if !carry { if !carry {
self.regs[REG_A] = self.regs[REG_A] << 1; self.regs[REG_A] <<= 1;
} else { } else {
self.regs[REG_A] = self.regs[REG_A] << 1 | 1; self.regs[REG_A] <<= 1;
self.regs[REG_A] |= 1;
} }
self.clear_flag(FLAG_Z); self.clear_flag(FLAG_Z);
self.clear_flag(FLAG_N); self.clear_flag(FLAG_N);
@ -1060,8 +1073,11 @@ impl CPU {
4 4
} }
0x18 => { 0x18 => {
let dst = self.load_args(1)[0]; let dst = u8::try_from(self.load_args(1)).unwrap();
self.jmp_r(dst); self.jmp_r(dst);
if self.debug {
println!("JMPR {:02X}", dst);
}
12 12
} }
0x19 => self.add_rr_rr(REG_N_H, REG_N_L, REG_N_D, REG_N_E), 0x19 => self.add_rr_rr(REG_N_H, REG_N_L, REG_N_D, REG_N_E),
@ -1086,9 +1102,10 @@ impl CPU {
let val = self.regs[REG_A]; let val = self.regs[REG_A];
self.set_clear_flag(FLAG_C, val & 1 == 1); self.set_clear_flag(FLAG_C, val & 1 == 1);
if !carry { if !carry {
self.regs[REG_A] = self.regs[REG_A] >> 1; self.regs[REG_A] >>= 1;
} else { } else {
self.regs[REG_A] = self.regs[REG_A] >> 1 | 0x80; self.regs[REG_A] >>= 1;
self.regs[REG_A] |= 0x80;
} }
self.clear_flag(FLAG_Z); self.clear_flag(FLAG_Z);
self.clear_flag(FLAG_N); self.clear_flag(FLAG_N);
@ -1098,7 +1115,7 @@ impl CPU {
0x20 => { 0x20 => {
let c = self.flags & FLAG_Z == 0; let c = self.flags & FLAG_Z == 0;
self.jmp_r_condition("NZ".to_owned(), c) self.jmp_r_condition("NZ", c)
} }
0x21 => self.ld_rr_vv(REG_N_H, REG_N_L), 0x21 => self.ld_rr_vv(REG_N_H, REG_N_L),
0x22 => { 0x22 => {
@ -1154,7 +1171,7 @@ impl CPU {
} }
0x28 => { 0x28 => {
let c = self.flags & FLAG_Z == FLAG_Z; let c = self.flags & FLAG_Z == FLAG_Z;
self.jmp_r_condition("Z".to_owned(), c) self.jmp_r_condition("Z", c)
} }
0x29 => self.add_rr_rr(REG_N_H, REG_N_L, REG_N_H, REG_N_L), 0x29 => self.add_rr_rr(REG_N_H, REG_N_L, REG_N_H, REG_N_L),
0x2A => { 0x2A => {
@ -1182,11 +1199,11 @@ impl CPU {
0x30 => { 0x30 => {
let c = self.flags & FLAG_C == 0; let c = self.flags & FLAG_C == 0;
self.jmp_r_condition("NC".to_owned(), c) self.jmp_r_condition("NC", c)
} }
0x31 => { 0x31 => {
let args = self.load_args(2); let args = self.load_args(2);
self.sp = to_u16(args); self.sp = u16::try_from(args).unwrap();
if self.debug { if self.debug {
println!("LD SP, {:04x}", self.sp); println!("LD SP, {:04x}", self.sp);
} }
@ -1223,7 +1240,7 @@ impl CPU {
} }
0x38 => { 0x38 => {
let c = self.flags & FLAG_C == FLAG_C; let c = self.flags & FLAG_C == FLAG_C;
self.jmp_r_condition("C".to_owned(), c) self.jmp_r_condition("C", c)
} }
0x39 => { 0x39 => {
if self.debug { if self.debug {
@ -1395,15 +1412,15 @@ impl CPU {
0xC0 => { 0xC0 => {
let c = self.flags & FLAG_Z == 0; let c = self.flags & FLAG_Z == 0;
self.ret_condition("NZ".to_owned(), c) self.ret_condition("NZ", c)
} }
0xC1 => self.pop_rr(REG_N_B, REG_N_C), 0xC1 => self.pop_rr(REG_N_B, REG_N_C),
0xC2 => { 0xC2 => {
let c = self.flags & FLAG_Z == 0; let c = self.flags & FLAG_Z == 0;
self.jmp_p_condition("NZ".to_owned(), c) self.jmp_p_condition("NZ", c)
} }
0xC3 => { 0xC3 => {
let dst = to_u16(self.load_args(2)); let dst = u16::try_from(self.load_args(2)).unwrap();
if self.debug { if self.debug {
println!("JMP {:04X}", dst); println!("JMP {:04X}", dst);
} }
@ -1412,11 +1429,11 @@ impl CPU {
} }
0xC4 => { 0xC4 => {
let c = self.flags & FLAG_Z == 0; let c = self.flags & FLAG_Z == 0;
self.call_condition("NZ".to_owned(), c) self.call_condition("NZ", c)
} }
0xC5 => self.push_rr(REG_N_B, REG_N_C), 0xC5 => self.push_rr(REG_N_B, REG_N_C),
0xC6 => { 0xC6 => {
let val = self.load_args(1)[0]; let val = u8::try_from(self.load_args(1)).unwrap();
if self.debug { if self.debug {
println!("ADD A, {:02X}", val); println!("ADD A, {:02X}", val);
} }
@ -1428,7 +1445,7 @@ impl CPU {
0xC7 => self.rst(0x00), 0xC7 => self.rst(0x00),
0xC8 => { 0xC8 => {
let c = self.flags & FLAG_Z == FLAG_Z; let c = self.flags & FLAG_Z == FLAG_Z;
self.ret_condition("Z".to_owned(), c) self.ret_condition("Z", c)
} }
0xC9 => { 0xC9 => {
if self.debug { if self.debug {
@ -1439,7 +1456,7 @@ impl CPU {
} }
0xCA => { 0xCA => {
let c = self.flags & FLAG_Z == FLAG_Z; let c = self.flags & FLAG_Z == FLAG_Z;
self.jmp_p_condition("Z".to_owned(), c) self.jmp_p_condition("Z", c)
} }
0xCB => { 0xCB => {
self.run_prefix_instruction(); self.run_prefix_instruction();
@ -1447,11 +1464,11 @@ impl CPU {
} }
0xCC => { 0xCC => {
let c = self.flags & FLAG_Z == FLAG_Z; let c = self.flags & FLAG_Z == FLAG_Z;
self.call_condition("Z".to_owned(), c) self.call_condition("Z", c)
} }
0xCD => self.call_condition("".to_owned(), true), 0xCD => self.call_condition("", true),
0xCE => { 0xCE => {
let arg = self.load_args(1)[0]; let arg = u8::try_from(self.load_args(1)).unwrap();
if self.debug { if self.debug {
println!("ADC A, {:02X}", arg); println!("ADC A, {:02X}", arg);
} }
@ -1462,21 +1479,21 @@ impl CPU {
0xD0 => { 0xD0 => {
let c = self.flags & FLAG_C == 0; let c = self.flags & FLAG_C == 0;
self.ret_condition("NC".to_owned(), c) self.ret_condition("NC", c)
} }
0xD1 => self.pop_rr(REG_N_D, REG_N_E), 0xD1 => self.pop_rr(REG_N_D, REG_N_E),
0xD2 => { 0xD2 => {
let c = self.flags & FLAG_C == 0; let c = self.flags & FLAG_C == 0;
self.jmp_p_condition("NC".to_owned(), c) self.jmp_p_condition("NC", c)
} }
0xD3 => panic!("NON-EXISTING OPCODE"), 0xD3 => panic!("NON-EXISTING OPCODE"),
0xD4 => { 0xD4 => {
let c = self.flags & FLAG_C == 0; let c = self.flags & FLAG_C == 0;
self.call_condition("NC".to_owned(), c) self.call_condition("NC", c)
} }
0xD5 => self.push_rr(REG_N_D, REG_N_E), 0xD5 => self.push_rr(REG_N_D, REG_N_E),
0xD6 => { 0xD6 => {
let val = self.load_args(1)[0]; let val = u8::try_from(self.load_args(1)).unwrap();
if self.debug { if self.debug {
println!("SUB {:02X}", val); println!("SUB {:02X}", val);
} }
@ -1487,7 +1504,7 @@ impl CPU {
0xD7 => self.rst(0x10), 0xD7 => self.rst(0x10),
0xD8 => { 0xD8 => {
let c = self.flags & FLAG_C == FLAG_C; let c = self.flags & FLAG_C == FLAG_C;
self.ret_condition("C".to_owned(), c) self.ret_condition("C", c)
} }
0xD9 => { 0xD9 => {
if self.debug { if self.debug {
@ -1497,16 +1514,16 @@ impl CPU {
} }
0xDA => { 0xDA => {
let c = self.flags & FLAG_C == FLAG_C; let c = self.flags & FLAG_C == FLAG_C;
self.jmp_p_condition("C".to_owned(), c) self.jmp_p_condition("C", c)
} }
0xDB => panic!("NON-EXISTING OPCODE"), 0xDB => panic!("NON-EXISTING OPCODE"),
0xDC => { 0xDC => {
let c = self.flags & FLAG_C == FLAG_C; let c = self.flags & FLAG_C == FLAG_C;
self.call_condition("C".to_owned(), c) self.call_condition("C", c)
} }
0xDD => panic!("NON-EXISTING OPCODE"), 0xDD => panic!("NON-EXISTING OPCODE"),
0xDE => { 0xDE => {
let arg = self.load_args(1)[0]; let arg = u8::try_from(self.load_args(1)).unwrap();
if self.debug { if self.debug {
println!("SBC {:02X}", arg); println!("SBC {:02X}", arg);
} }
@ -1516,12 +1533,12 @@ impl CPU {
0xDF => self.rst(0x18), 0xDF => self.rst(0x18),
0xE0 => { 0xE0 => {
let args = self.load_args(1); let arg = u8::try_from(self.load_args(1)).unwrap();
if self.debug { if self.debug {
println!("LDH {:02X}, A", args[0]); println!("LDH {:02X}, A", arg);
} }
self.interconnect self.interconnect
.write_byte(0xFF00 + args[0] as u16, self.regs[REG_A]); .write_byte(0xFF00 + arg as u16, self.regs[REG_A]);
12 12
} }
0xE1 => self.pop_rr(REG_N_H, REG_N_L), 0xE1 => self.pop_rr(REG_N_H, REG_N_L),
@ -1536,7 +1553,7 @@ impl CPU {
0xE3 | 0xE4 => panic!("NON-EXISTING OPCODE"), 0xE3 | 0xE4 => panic!("NON-EXISTING OPCODE"),
0xE5 => self.push_rr(REG_N_H, REG_N_L), 0xE5 => self.push_rr(REG_N_H, REG_N_L),
0xE6 => { 0xE6 => {
let val = self.load_args(1)[0]; let val = u8::try_from(self.load_args(1)).unwrap();
if self.debug { if self.debug {
println!("AND {:02X}", val); println!("AND {:02X}", val);
} }
@ -1551,16 +1568,15 @@ impl CPU {
} }
0xE7 => self.rst(0x20), 0xE7 => self.rst(0x20),
0xE8 => { 0xE8 => {
let arg = self.load_args(1)[0] as i8; let arg = u8::try_from(self.load_args(1)).unwrap() as i8;
if self.debug { if self.debug {
println!("ADD SP, {:02X}", arg); println!("ADD SP, {:02X}", arg);
} }
let t: u16; let t = if arg > 0 {
if arg > 0 { self.sp.wrapping_add(arg as u16)
t = self.sp.wrapping_add(arg as u16);
} else { } else {
t = self.sp.wrapping_sub((-arg) as u16); self.sp.wrapping_sub((-arg) as u16)
} };
let sp = self.sp; let sp = self.sp;
self.clear_flag(FLAG_N); self.clear_flag(FLAG_N);
self.clear_flag(FLAG_Z); self.clear_flag(FLAG_Z);
@ -1577,7 +1593,7 @@ impl CPU {
4 4
} }
0xEA => { 0xEA => {
let addr = to_u16(self.load_args(2)); let addr = u16::try_from(self.load_args(2)).unwrap();
if self.debug { if self.debug {
println!("LD ({:04X}), A", addr); println!("LD ({:04X}), A", addr);
} }
@ -1586,7 +1602,7 @@ impl CPU {
} }
0xEB..=0xED => panic!("NON-EXISTING OPCODE"), 0xEB..=0xED => panic!("NON-EXISTING OPCODE"),
0xEE => { 0xEE => {
let arg = self.load_args(1)[0]; let arg = u8::try_from(self.load_args(1)).unwrap();
if self.debug { if self.debug {
println!("XOR {:02X}", arg); println!("XOR {:02X}", arg);
} }
@ -1601,11 +1617,11 @@ impl CPU {
0xEF => self.rst(0x28), 0xEF => self.rst(0x28),
0xF0 => { 0xF0 => {
let args = self.load_args(1); let arg = u8::try_from(self.load_args(1)).unwrap();
if self.debug { if self.debug {
println!("LDH A, {:02X}", args[0]); println!("LDH A, {:02X}", arg);
} }
self.regs[REG_A] = self.interconnect.read_byte(0xFF00 + args[0] as u16); self.regs[REG_A] = self.interconnect.read_byte(0xFF00 + arg as u16);
12 12
} }
0xF1 => self.pop_rr(REG_N_A, REG_N_F), 0xF1 => self.pop_rr(REG_N_A, REG_N_F),
@ -1627,7 +1643,7 @@ impl CPU {
0xF4 => panic!("NON-EXISTING OPCODE"), 0xF4 => panic!("NON-EXISTING OPCODE"),
0xF5 => self.push_rr(REG_N_A, REG_N_F), 0xF5 => self.push_rr(REG_N_A, REG_N_F),
0xF6 => { 0xF6 => {
let val = self.load_args(1)[0]; let val = u8::try_from(self.load_args(1)).unwrap();
if self.debug { if self.debug {
println!("OR {:02X}", val); println!("OR {:02X}", val);
} }
@ -1641,7 +1657,7 @@ impl CPU {
} }
0xF7 => self.rst(0x30), 0xF7 => self.rst(0x30),
0xF8 => { 0xF8 => {
let arg = self.load_args(1)[0] as i8; let arg = u8::try_from(self.load_args(1)).unwrap() as i8;
if self.debug { if self.debug {
println!("LD HL, SP+{:02X}", arg); println!("LD HL, SP+{:02X}", arg);
} }
@ -1671,7 +1687,7 @@ impl CPU {
8 8
} }
0xFA => { 0xFA => {
let addr = to_u16(self.load_args(2)); let addr = u16::try_from(self.load_args(2)).unwrap();
if self.debug { if self.debug {
println!("LD A, ({:04X})", addr); println!("LD A, ({:04X})", addr);
} }
@ -1679,7 +1695,6 @@ impl CPU {
16 16
} }
0xFB => { 0xFB => {
// Enable interrupts - TODO
if self.debug { if self.debug {
println!("EI"); println!("EI");
} }
@ -1689,19 +1704,25 @@ impl CPU {
0xFC | 0xFD => panic!("NON-EXISTING OPCODE"), 0xFC | 0xFD => panic!("NON-EXISTING OPCODE"),
0xFE => { 0xFE => {
let args = self.load_args(1); let arg = u8::try_from(self.load_args(1)).unwrap();
if self.debug { if self.debug {
println!("CP {:02X}", args[0]); println!("CP {:02X}", arg);
} }
self.cp_r(args[0]); self.cp_r(arg);
8 8
} }
0xFF => self.rst(0x38), 0xFF => self.rst(0x38),
_ => panic!("Unknown instruction: {:02x}", instruction),
}; };
} }
self.interconnect.tick(cycles);
cycles if self.interconnect.tick(cycles) == TickResult::Shutdown {
return CpuTickResult::Shutdown {
cycles_executed: cycles as usize,
};
}
CpuTickResult::Continue {
cycles_executed: cycles as usize,
}
} }
} }

View File

@ -1,6 +1,13 @@
extern crate libc; extern crate libc;
extern crate sdl2; extern crate sdl2;
mod palette;
mod structs;
use palette::{CgbPalette, DmgPalette};
use structs::*;
use std::io::Write;
// Internal ram size // Internal ram size
const VRAM_SIZE: usize = 0x2000; const VRAM_SIZE: usize = 0x2000;
@ -41,32 +48,8 @@ const STAT_MODE_HBLANK_INT: u8 = 1 << 3;
const SPRITE_OBJ_BG_PRIORITY: u8 = 1 << 7; const SPRITE_OBJ_BG_PRIORITY: u8 = 1 << 7;
const SPRITE_Y_FLIP: u8 = 1 << 6; const SPRITE_Y_FLIP: u8 = 1 << 6;
const SPRITE_X_FLIP: u8 = 1 << 5; const SPRITE_X_FLIP: u8 = 1 << 5;
const SPRITE_PALETTE_NO: u8 = 1 << 4; // NonCGB only //const SPRITE_PALETTE_NO: u8 = 1 << 4; // NonCGB only
// const SPRITE_TILE_VRAM_BANK: u8 = 1 << 3; // CGB only const SPRITE_TILE_VRAM_BANK: u8 = 1 << 3; // CGB only
// Display color
/*
const MONOCHROME_PALETTE: &'static [[f64; 3]; 4] = &[
[0.605, 0.734, 0.059],
[0.543, 0.672, 0.059],
[0.188, 0.383, 0.188],
[0.059, 0.219, 0.059],
];
*/
const MONOCHROME_PALETTE: &'static [[u8; 3]; 4] = &[
[255, 255, 255],
[200, 200, 200],
[125, 125, 12],
[50, 50, 50],
];
#[derive(Debug, Copy, Clone)]
enum PixelOrigin {
Empty,
Background,
Window,
Sprite,
}
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
struct Pixel { struct Pixel {
@ -83,7 +66,7 @@ impl Default for Pixel {
} }
} }
#[derive(Debug)] #[derive(Debug, PartialEq)]
enum DisplayMode { enum DisplayMode {
ReadOAMMemory, ReadOAMMemory,
ReadFullMemory, ReadFullMemory,
@ -97,45 +80,12 @@ impl Default for DisplayMode {
} }
} }
struct Sprite {
x: u8,
y: u8,
tile: u8,
flags: u8,
}
impl Sprite {
fn is_hidden(&self) -> bool {
self.x == 0 || self.y == 0
}
fn is_foreground(&self) -> bool {
(self.flags & SPRITE_OBJ_BG_PRIORITY) == 0
}
fn is_x_flipped(&self) -> bool {
(self.flags & SPRITE_X_FLIP) == SPRITE_X_FLIP
}
fn is_y_flipped(&self) -> bool {
(self.flags & SPRITE_Y_FLIP) == SPRITE_Y_FLIP
}
fn palette(&self) -> u8 {
if (self.flags & SPRITE_PALETTE_NO) == 0 {
0
} else {
1
}
}
}
pub struct Display { pub struct Display {
control: u8, control: u8,
status: u8, status: u8,
background_palette: u8, background_palette: DmgPalette,
object_palette_0: u8, // Only 0 and 1 for GB
object_palette_1: u8, object_palette: [DmgPalette; 2],
scrollx: u8, scrollx: u8,
scrolly: u8, scrolly: u8,
windowx: u8, windowx: u8,
@ -143,12 +93,14 @@ pub struct Display {
curline: u8, curline: u8,
lyc: u8, lyc: u8,
vram: Box<[u8]>, vram0: Box<[u8]>,
vram1: Box<[u8]>,
oam: Box<[u8]>, oam: Box<[u8]>,
vram_bank: u8,
current_ticks: u16, current_ticks: u16,
current_mode: DisplayMode, current_mode: DisplayMode,
// TODO
renderer: sdl2::render::Canvas<sdl2::video::Window>, renderer: sdl2::render::Canvas<sdl2::video::Window>,
pub event_pump: sdl2::EventPump, pub event_pump: sdl2::EventPump,
@ -160,6 +112,15 @@ pub struct Display {
frameskip: u8, frameskip: u8,
frame_no: u8, frame_no: u8,
// GBC:
background_palette_autoinc: bool,
background_palette_index: u8,
background_palette_cgb: [CgbPalette; 0x40 / 8],
object_palette_autoinc: bool,
object_palette_index: u8,
object_palette_cgb: [CgbPalette; 0x40 / 8],
} }
impl Display { impl Display {
@ -178,9 +139,8 @@ impl Display {
Display { Display {
control: 0, control: 0,
status: 0, status: 0,
background_palette: 0, background_palette: Default::default(),
object_palette_0: 0, object_palette: Default::default(),
object_palette_1: 0,
scrollx: 0, scrollx: 0,
scrolly: 0, scrolly: 0,
windowx: 0, windowx: 0,
@ -190,28 +150,35 @@ impl Display {
current_ticks: 0, current_ticks: 0,
current_mode: DisplayMode::default(), current_mode: DisplayMode::default(),
vram: vec![0; VRAM_SIZE].into_boxed_slice(), vram0: vec![0; VRAM_SIZE].into_boxed_slice(),
vram1: vec![0; VRAM_SIZE].into_boxed_slice(),
vram_bank: 0,
oam: vec![0; OAM_SIZE].into_boxed_slice(), oam: vec![0; OAM_SIZE].into_boxed_slice(),
renderer: renderer, renderer,
event_pump: event_pump, event_pump,
vblank_interrupt: false, vblank_interrupt: false,
stat_interrupt: false, stat_interrupt: false,
pixels: pixels, pixels,
frameskip: 0, frameskip: 0,
frame_no: 0, frame_no: 0,
background_palette_autoinc: false,
background_palette_index: 0,
background_palette_cgb: [CgbPalette([0u8; 2 * 4]); 0x40 / 8],
object_palette_autoinc: false,
object_palette_index: 0,
object_palette_cgb: [CgbPalette([0u8; 2 * 4]); 0x40 / 8],
} }
} }
#[inline]
fn set_pixel(&mut self, x: u8, y: u8, color: sdl2::pixels::Color, origin: PixelOrigin) { fn set_pixel(&mut self, x: u8, y: u8, color: sdl2::pixels::Color, origin: PixelOrigin) {
let p = &mut self.pixels[(x as usize) + (y as usize) * GB_PIXELS_X]; let p = &mut self.pixels[(x as usize) + (y as usize) * GB_PIXELS_X];
p.color = color; p.color = color;
p.origin = origin; p.origin = origin;
} }
#[inline]
fn get_pixel_origin(&self, x: u8, y: u8) -> PixelOrigin { fn get_pixel_origin(&self, x: u8, y: u8) -> PixelOrigin {
self.pixels[(x as usize) + (y as usize) * GB_PIXELS_X].origin self.pixels[(x as usize) + (y as usize) * GB_PIXELS_X].origin
} }
@ -247,10 +214,9 @@ impl Display {
.expect("Rendering failed"); .expect("Rendering failed");
} }
#[inline]
pub fn vblank_interrupt(&mut self) -> bool { pub fn vblank_interrupt(&mut self) -> bool {
// Returns whether or not a vblank interrupt should be done // Returns whether or not a vblank interrupt should be done
// Yes, this is polling, and yes, this sucks.\ // Yes, this is polling, and yes, this sucks.
if self.vblank_interrupt { if self.vblank_interrupt {
self.vblank_interrupt = false; self.vblank_interrupt = false;
true true
@ -259,7 +225,6 @@ impl Display {
} }
} }
#[inline]
pub fn stat_interrupt(&mut self) -> bool { pub fn stat_interrupt(&mut self) -> bool {
if self.stat_interrupt { if self.stat_interrupt {
self.stat_interrupt = false; self.stat_interrupt = false;
@ -269,10 +234,15 @@ impl Display {
} }
} }
#[inline]
pub fn write_byte(&mut self, addr: u16, val: u8) { pub fn write_byte(&mut self, addr: u16, val: u8) {
match addr { match addr {
0x8000..=0x9FFF => self.vram[(addr - 0x8000) as usize] = val, 0x8000..=0x9FFF => {
if (self.vram_bank & 1) == 0 {
self.vram0[(addr - 0x8000) as usize] = val
} else {
self.vram1[(addr - 0x8000) as usize] = val
}
}
0xFE00..=0xFE9F => self.oam[(addr - 0xFE00) as usize] = val, 0xFE00..=0xFE9F => self.oam[(addr - 0xFE00) as usize] = val,
0xFF40 => self.control = val, 0xFF40 => self.control = val,
0xFF41 => self.status = val, 0xFF41 => self.status = val,
@ -280,9 +250,47 @@ impl Display {
0xFF43 => self.scrollx = val, 0xFF43 => self.scrollx = val,
0xFF44 => self.curline = 0, 0xFF44 => self.curline = 0,
0xFF45 => self.lyc = val, 0xFF45 => self.lyc = val,
0xFF47 => self.background_palette = val, // GB classic
0xFF48 => self.object_palette_0 = val, 0xFF47 => self.background_palette.0 = val,
0xFF49 => self.object_palette_1 = val, 0xFF48 => self.object_palette[0].0 = val,
0xFF49 => self.object_palette[1].0 = val,
// GBC
0xFF68 => {
self.background_palette_index = val & 0b0111_1111;
self.background_palette_autoinc = (val >> 7) != 0;
}
0xFF69 => {
if self.current_mode == DisplayMode::ReadFullMemory {
println!("Trying to write to palette memory while being accessed!");
}
let idx = self.background_palette_index as usize;
if idx < 64 {
self.background_palette_cgb[idx / 8].0[idx % 8] = val;
} else {
println!("OOB palette w/ autoinc");
return;
}
if self.background_palette_autoinc {
self.background_palette_index += 1;
}
}
0xFF6A => {
self.object_palette_index = val & 0b0111_1111;
self.object_palette_autoinc = (val >> 7) != 0;
}
0xFF6B => {
let idx = self.object_palette_index as usize;
if idx < 64 {
self.object_palette_cgb[idx / 8].0[idx % 8] = val;
} else {
println!("OOB obj palette w/ autoinc");
return;
}
if self.object_palette_autoinc {
self.object_palette_index += 1;
}
}
0xFF4A => { 0xFF4A => {
if self.windowy != val { if self.windowy != val {
println!("WY set to {:02X}", val); println!("WY set to {:02X}", val);
@ -295,14 +303,20 @@ impl Display {
} }
self.windowx = val; self.windowx = val;
} }
0xFF4F => self.vram_bank = val,
_ => panic!("Display: Write {:02X} to {:04X} unsupported", val, addr), _ => panic!("Display: Write {:02X} to {:04X} unsupported", val, addr),
} }
} }
#[inline]
pub fn read_byte(&self, addr: u16) -> u8 { pub fn read_byte(&self, addr: u16) -> u8 {
match addr { match addr {
0x8000..=0x9FFF => self.vram[(addr - 0x8000) as usize], 0x8000..=0x9FFF => {
if (self.vram_bank & 1) == 0 {
self.vram0[(addr - 0x8000) as usize]
} else {
self.vram1[(addr - 0x8000) as usize]
}
}
0xFE00..=0xFE9F => self.oam[(addr - 0xFE00) as usize], 0xFE00..=0xFE9F => self.oam[(addr - 0xFE00) as usize],
0xFF40 => self.control, 0xFF40 => self.control,
0xFF41 => self.status, 0xFF41 => self.status,
@ -310,16 +324,16 @@ impl Display {
0xFF43 => self.scrollx, 0xFF43 => self.scrollx,
0xFF44 => self.curline, 0xFF44 => self.curline,
0xFF45 => self.lyc, 0xFF45 => self.lyc,
0xFF47 => self.background_palette, 0xFF47 => self.background_palette.0,
0xFF48 => self.object_palette_0, 0xFF48 => self.object_palette[0].0,
0xFF49 => self.object_palette_1, 0xFF49 => self.object_palette[1].0,
0xFF4A => self.windowy, 0xFF4A => self.windowy,
0xFF4B => self.windowx, 0xFF4B => self.windowx,
0xFF4F => self.vram_bank | 0b1111_1110,
_ => panic!("Display: Read from {:04X} unsupported", addr), _ => panic!("Display: Read from {:04X} unsupported", addr),
} }
} }
#[inline]
pub fn tick(&mut self, ticks: u16) { pub fn tick(&mut self, ticks: u16) {
self.status &= 0xFC; self.status &= 0xFC;
if self.control & CTRL_LCD_DISPLAY_ENABLE == 0 { if self.control & CTRL_LCD_DISPLAY_ENABLE == 0 {
@ -419,15 +433,14 @@ impl Display {
} }
} }
fn render_sprites(&mut self, sprites: &Vec<Sprite>) { fn render_sprites(&mut self, sprites: &[Sprite]) {
if self.control & CTRL_BG_SPRITE_ENABLE > 0 { if self.control & CTRL_BG_SPRITE_ENABLE > 0 {
let mut num_rendered: u8 = 0; let mut num_rendered: u8 = 0;
for i in 0..39 { for sprite in sprites.iter().take(39) {
// Gameboy limitation // Gameboy limitation
if num_rendered > 10 { if num_rendered >= 10 {
break; break;
} }
let sprite = &sprites[i];
// Skip hidden sprites // Skip hidden sprites
if sprite.is_hidden() { if sprite.is_hidden() {
@ -437,36 +450,32 @@ impl Display {
// Calculate correct coords // Calculate correct coords
let x: u8 = sprite.x.wrapping_sub(8); let x: u8 = sprite.x.wrapping_sub(8);
let y: u8 = sprite.y.wrapping_sub(16); let y: u8 = sprite.y.wrapping_sub(16);
let render_y = self.curline; let render_y = self.curline;
// Is this sprite on the current line? // Is this sprite on the current line?
if y.wrapping_add(8) >= render_y && y <= render_y { let wide_mode = self.control & CTRL_BG_SPRITE_SIZE > 0;
let actual_h = if wide_mode { 16 } else { 8 };
if y.wrapping_add(actual_h) > render_y && y <= render_y {
num_rendered += 1; num_rendered += 1;
// Flip sprite, TODO: Validate let tile_offset_y = if sprite.is_y_flipped() {
let y_o: u8 = match sprite.is_y_flipped() { render_y as usize - (y as usize) ^ (actual_h as usize - 1)
true => y ^ 7, } else {
false => y, render_y as usize - y as usize
}; };
let tile_offset_y: usize = render_y as usize - y_o as usize;
let tile_base_addr: usize = sprite.tile as usize * 16; // Should this be twice as wide in wide mode? let tile_base_addr: usize = sprite.tile as usize * 16;
let tile_addr = tile_base_addr + tile_offset_y * 2; let tile_addr = tile_base_addr + tile_offset_y * 2;
let tile_line_1 = self.vram[tile_addr + 0]; let vram = if sprite.vram_bank() == 0 {
let tile_line_2 = self.vram[tile_addr + 1]; &*self.vram0
let tile_line_3 = self.vram[tile_addr + 2]; } else {
let tile_line_4 = self.vram[tile_addr + 3]; &*self.vram1
// We need to draw this.
let wide_mode = self.control & CTRL_BG_SPRITE_SIZE > 0;
if wide_mode {
// panic!("TODO");
}
let limit = match wide_mode {
true => 16,
false => 8,
}; };
for x_o in 0..limit { let tile_line_1 = vram[tile_addr + 0];
let tile_line_2 = vram[tile_addr + 1];
for x_o in 0..8 {
let b1: bool; let b1: bool;
let b2: bool; let b2: bool;
@ -476,55 +485,37 @@ impl Display {
// Do not draw if the sprite should be drawn in the background // Do not draw if the sprite should be drawn in the background
if !sprite.is_foreground() { if !sprite.is_foreground() {
match pixel_origin { match pixel_origin {
PixelOrigin::Background | PixelOrigin::Window => continue, PixelOrigin::Background(0) => {}
PixelOrigin::Window | PixelOrigin::Background(_) => continue,
_ => {} _ => {}
} }
} }
if wide_mode && x_o > 7 { if pixel_origin == PixelOrigin::BackgroundPriority {
let x_o2: u8 = match sprite.is_x_flipped() {
true => (x_o - 8) ^ 7,
false => (x_o - 8),
};
b1 = (tile_line_3 & 1 << (7 - x_o2)) > 0;
b2 = (tile_line_4 & 1 << (7 - x_o2)) > 0;
} else {
let x_o2: u8 = match sprite.is_x_flipped() {
true => x_o ^ 7,
false => x_o,
};
b1 = (tile_line_1 & 1 << (7 - x_o2)) > 0;
b2 = (tile_line_2 & 1 << (7 - x_o2)) > 0;
}
// Sprites may be transparent.
if !b1 && !b2 {
continue; continue;
} }
let c = (b1 as u8) * 2 + b2 as u8; let x_maybe_flipped: u8 = if sprite.is_x_flipped() { x_o ^ 7 } else { x_o };
let lookup: [u8; 4] = match sprite.palette() { b1 = (tile_line_1 & 1 << (7 - x_o)) > 0;
0 => [ b2 = (tile_line_2 & 1 << (7 - x_o)) > 0;
self.object_palette_0 & 3,
(self.object_palette_0 >> 2) & 3,
(self.object_palette_0 >> 4) & 3,
(self.object_palette_0 >> 6) & 3,
],
1 => [
self.object_palette_1 & 3,
(self.object_palette_1 >> 2) & 3,
(self.object_palette_1 >> 4) & 3,
(self.object_palette_1 >> 6) & 3,
],
_ => unreachable!(),
};
let entry = MONOCHROME_PALETTE[lookup[c as usize] as usize];
// Draw stuff. We're currently only in monochrome mode // Sprites may be transparent.
if !b1 && !b2 && sprite.is_foreground() {
continue;
}
let c = ((b2 as u8) * 2 + b1 as u8) as usize;
if c == 0 {
continue;
}
// DMG:
// let c = self.object_palette[sprite.palette() as usize].get_color(c);
// GBC:
let c = self.object_palette_cgb[sprite.palette() as usize].get_color(c);
self.set_pixel( self.set_pixel(
x.wrapping_add(x_o), x.wrapping_add(x_maybe_flipped),
render_y, render_y,
sdl2::pixels::Color::RGB(entry[0], entry[1], entry[2]), c,
PixelOrigin::Sprite, PixelOrigin::Sprite,
); );
} }
@ -533,7 +524,6 @@ impl Display {
} }
} }
#[inline]
fn get_bg_window_tile_addr(&self, tile_id: u8) -> usize { fn get_bg_window_tile_addr(&self, tile_id: u8) -> usize {
if (self.control & CTRL_BG_WINDOW_TILE_DATA_SELECT) == CTRL_BG_WINDOW_TILE_DATA_SELECT { if (self.control & CTRL_BG_WINDOW_TILE_DATA_SELECT) == CTRL_BG_WINDOW_TILE_DATA_SELECT {
let base_addr = 0x0000; let base_addr = 0x0000;
@ -545,7 +535,17 @@ impl Display {
} }
} }
#[inline] pub fn dump_vram(&self) {
std::fs::File::create("vram0.dat")
.unwrap()
.write_all(&self.vram0)
.unwrap();
std::fs::File::create("vram1.dat")
.unwrap()
.write_all(&self.vram1)
.unwrap();
}
fn renderscan(&mut self) { fn renderscan(&mut self) {
// Points to the background map offset to use. // Points to the background map offset to use.
let background_map: usize; let background_map: usize;
@ -571,132 +571,140 @@ impl Display {
// Order sprites by priority // Order sprites by priority
let mut queue: Vec<Sprite> = Vec::new(); let mut queue: Vec<Sprite> = Vec::new();
for i in 0..39 { for i in 0..39 {
queue.push(Sprite { queue.push(Sprite::load(&self.oam[i * 4..(i + 1) * 4]));
y: self.oam[i * 4 + 0],
x: self.oam[i * 4 + 1],
tile: self.oam[i * 4 + 2],
flags: self.oam[i * 4 + 3],
});
} }
// This is the non-CGB priority. // This is the non-CGB priority.
// Smaller x coord = higher. // Smaller x coord = higher.
use std::cmp; queue.sort_by(|x, y| x.x.cmp(&y.x));
queue.sort_by(|x, y| {
if x.x > y.x {
cmp::Ordering::Greater
} else if x.x < y.x {
cmp::Ordering::Less
} else {
cmp::Ordering::Equal
}
});
queue.reverse(); queue.reverse();
// Render background // Render background
if (self.control & CTRL_BG_DISPLAY) == CTRL_BG_DISPLAY { if (self.control & CTRL_BG_DISPLAY) == CTRL_BG_DISPLAY {
// Render pixels (20 tiles) // Render pixels (20 tiles)
let render_abs_y = map_offset_y.wrapping_add(render_y);
let tile_index_y: u8 = render_abs_y >> 3;
let tile_offset_y: u8 = render_abs_y & 7;
for render_x in 0..160 { for render_x in 0..160 {
// Absolute render coordinates // Absolute render coordinates
let render_abs_x = map_offset_x.wrapping_add(render_x); let render_abs_x = map_offset_x.wrapping_add(render_x);
let render_abs_y = map_offset_y.wrapping_add(render_y);
let tile_index_x: u8 = render_abs_x >> 3; let tile_index_x: u8 = render_abs_x >> 3;
let tile_offset_x: u8 = render_abs_x & 7; let tile_offset_x: u8 = render_abs_x & 7;
let tile_index_y: u8 = render_abs_y >> 3;
let tile_offset_y: u8 = render_abs_y & 7;
let vram_offset: usize = let vram_offset: usize =
background_map + ((tile_index_y as usize) * 32) + tile_index_x as usize; background_map + ((tile_index_y as usize) * 32) + tile_index_x as usize;
// Obtain tile ID in this area // Obtain tile ID in this area
let tile_id = self.vram[vram_offset]; let tile_id = self.vram0[vram_offset];
// GBC stuff
// Get BG map attributes
let bg_attribs = BgMapAttributes(self.vram1[vram_offset]);
let vram = [&*self.vram0, &*self.vram1][bg_attribs.vram_bank_number()];
let tile_offset_y = if bg_attribs.vertical_flip() {
7 ^ tile_offset_y
} else {
tile_offset_y
};
// Obtain tile information // Obtain tile information
let tile_base_addr: usize = self.get_bg_window_tile_addr(tile_id); let tile_base_addr: usize = self.get_bg_window_tile_addr(tile_id);
let addr = tile_base_addr + (tile_offset_y as usize) * 2; let addr = tile_base_addr + (tile_offset_y as usize) * 2; // two bytes per row
let tile_line_1 = self.vram[addr];
let tile_line_2 = self.vram[addr + 1]; let tile_line_1 = vram[addr];
let tile_line_2 = vram[addr + 1];
// Get the correct bit // Get the correct bit
let b1: bool = (tile_line_1 & 1 << (7 - tile_offset_x)) != 0; let b1 = if bg_attribs.horizontal_flip() {
let b2: bool = (tile_line_2 & 1 << (7 - tile_offset_x)) != 0; 7 ^ (1 << (7 - tile_offset_x))
} else {
1 << (7 - tile_offset_x)
};
let b2 = if bg_attribs.horizontal_flip() {
7 ^ (1 << (7 - tile_offset_x))
} else {
1 << (7 - tile_offset_x)
};
let b1: bool = (tile_line_1 & b1) != 0;
let b2: bool = (tile_line_2 & b2) != 0;
// Lookup the color // Lookup the color
let c = (b1 as u8) * 2 + b2 as u8; let c = ((b2 as u8) * 2 + b1 as u8) as usize;
let lookup: [u8; 4] = [ // let color = self.background_palette.get_color(c);
self.background_palette & 3, let color = self.background_palette_cgb[bg_attribs.palette_number()].get_color(c);
(self.background_palette >> 2) & 3, if bg_attribs.has_priority() && (self.control & 1) == 1 {
(self.background_palette >> 4) & 3, self.set_pixel(render_x, render_y, color, PixelOrigin::BackgroundPriority);
(self.background_palette >> 6) & 3, } else {
]; self.set_pixel(render_x, render_y, color, PixelOrigin::Background(c));
let entry = MONOCHROME_PALETTE[lookup[c as usize] as usize]; }
let origin = match c {
0 => PixelOrigin::Empty, // Hack so that objects will be in front of it.
_ => PixelOrigin::Background,
};
self.set_pixel(
render_x,
render_y,
sdl2::pixels::Color::RGB(entry[0], entry[1], entry[2]),
origin,
);
} }
} }
if (self.control & CTRL_WND_DISPLAY_ENABLE) == CTRL_WND_DISPLAY_ENABLE { if (self.control & CTRL_WND_DISPLAY_ENABLE) == CTRL_WND_DISPLAY_ENABLE {
// Draw 'window' over the background. // Draw 'window' over the background.
// Screen coordinates of the top left corner are WX-7, WY // Screen coordinates of the top left corner are WX-7, WY
// Quick check if the window is visible at all.
if self.windowx < 167 && self.windowy < 144 { if self.windowx < 167 && self.windowy < 144 {
//let rx = self.windowx.wrapping_add(7); let window_y_offset = render_y as i16 - self.windowy as i16;
let rx = 7u8.wrapping_sub(self.windowx); if self.windowx < 7 {
let ry = render_y.wrapping_add(self.windowy); eprintln!("Window X position < 7, bailing out");
for r_x in 0..160u8 { } else if window_y_offset >= 144 || window_y_offset < 0 {
let render_x = r_x.wrapping_add(rx); // Not visible
// Absolute render coordinates } else {
let tile_index_x: u8 = render_x >> 3; let window_y_offset = window_y_offset as u8;
let tile_offset_x: u8 = render_x & 7; let window_x_offset = self.windowx - 7;
let tile_index_y: u8 = ry >> 3;
let tile_offset_y: u8 = ry & 7;
let vram_offset: usize = for r_x in 0..(160u8 - window_x_offset) {
window_map + (tile_index_y as usize) * 32 + tile_index_x as usize; let render_x = r_x.wrapping_add(window_x_offset);
// Absolute render coordinates
let tile_index_x: u8 = render_x >> 3;
let tile_index_y: u8 = window_y_offset >> 3;
let tile_offset_x: u8 = render_x & 7;
let tile_offset_y: u8 = window_y_offset & 7;
// Obtain tile ID in this area let vram_offset: usize =
let tile_id = self.vram[vram_offset]; window_map + (tile_index_y as usize) * 32 + tile_index_x as usize;
// Obtain tile information // Obtain tile ID in this area
let tile_base_addr: usize = self.get_bg_window_tile_addr(tile_id); let tile_id = self.vram0[vram_offset];
let tile_addr = tile_base_addr + (tile_offset_y as usize) * 2; let bg_attribs = BgMapAttributes(self.vram1[vram_offset]);
let tile_line_1 = self.vram[tile_addr];
let tile_line_2 = self.vram[tile_addr + 1];
// Get the correct bit // Get BG map attributes
let b1: bool = (tile_line_1 & 1 << (7 - tile_offset_x)) > 0; let vram = if bg_attribs.vram_bank_number() == 0 {
let b2: bool = (tile_line_2 & 1 << (7 - tile_offset_x)) > 0; &*self.vram0
} else {
&*self.vram1
};
let c = (b1 as u8) * 2 + b2 as u8; let tile_offset_x = if bg_attribs.horizontal_flip() {
let lookup: [u8; 4] = [ 7 ^ tile_offset_x
self.background_palette & 3, } else {
(self.background_palette >> 2) & 3, tile_offset_x
(self.background_palette >> 4) & 3, };
(self.background_palette >> 6) & 3, let tile_offset_y = if bg_attribs.vertical_flip() {
]; 7 ^ tile_offset_y
let entry = MONOCHROME_PALETTE[lookup[c as usize] as usize]; } else {
tile_offset_y
};
// Draw stuff. We're currently only in monochrome mode // Obtain tile information
let origin = match c { let tile_base_addr: usize = self.get_bg_window_tile_addr(tile_id);
0 => PixelOrigin::Empty, // Hack so that objects will be in front of it. let tile_addr = tile_base_addr + (tile_offset_y as usize) * 2;
_ => PixelOrigin::Window, let tile_line_1 = vram[tile_addr];
}; let tile_line_2 = vram[tile_addr + 1];
self.set_pixel(
render_x, // Get the correct bit
render_y, let b1: bool = (tile_line_1 & 1 << (7 - tile_offset_x)) > 0;
sdl2::pixels::Color::RGB(entry[0], entry[1], entry[2]), let b2: bool = (tile_line_2 & 1 << (7 - tile_offset_x)) > 0;
origin,
); let c = (b2 as u8) * 2 + b1 as u8;
// let c = self.background_palette.get_color(c);
let c = self.background_palette_cgb[bg_attribs.palette_number()]
.get_color(c as usize);
self.set_pixel(render_x, render_y, c, PixelOrigin::Window);
}
} }
} }
} }

61
src/display/palette.rs Normal file
View File

@ -0,0 +1,61 @@
use super::sdl2;
#[derive(Copy, Clone, Default)]
pub struct DmgPalette(pub u8);
impl DmgPalette {
pub fn get_color(self, n: usize) -> sdl2::pixels::Color {
const MONOCHROME_PALETTE: &[[u8; 3]; 4] = &[
[255, 255, 255],
[200, 200, 200],
[125, 125, 12],
[50, 50, 50],
];
assert!(n < 4);
let c = self.0 >> (2 * n);
let n = c & 3;
let c = MONOCHROME_PALETTE[n as usize];
sdl2::pixels::Color::RGB(c[0], c[1], c[2])
}
}
#[derive(Copy, Clone)]
pub struct CgbPalette(pub [u8; 8]);
impl CgbPalette {
pub fn get_color(self, n: usize) -> sdl2::pixels::Color {
if n == 0 {
return sdl2::pixels::Color::RGB(255, 255, 255);
}
let v = ((self.0[2 * n + 1] as u16) << 8) | (self.0[2 * n] as u16);
let r = (v & 0b1_1111) as u8;
let g = ((v >> 5) & 0b1_1111) as u8;
let b = ((v >> 10) & 0b1_1111) as u8;
if false {
sdl2::pixels::Color::RGB(r << 3, g << 3, b << 3)
} else {
// According to some code:
// Real colors:
let r = r as u16;
let g = g as u16;
let b = b as u16;
let mapped_r = ((r * 13 + g * 2 + b) >> 1) as u8;
let mapped_g = ((g * 3 + b) << 1) as u8;
let mapped_b = ((r * 3 + g * 2 + b * 11) >> 1) as u8;
sdl2::pixels::Color::RGB(mapped_r, mapped_g, mapped_b)
}
}
}
impl std::fmt::Display for CgbPalette {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Palette: ")?;
for n in 0..4 {
let v = ((self.0[2 * n + 1] as u16) << 8) | (self.0[2 * n] as u16);
let r = (v & 0b1_1111) as u8;
let g = ((v >> 5) & 0b1_1111) as u8;
let b = ((v >> 10) & 0b1_1111) as u8;
write!(f, "{:02X}{:02X}{:02X} ", r, g, b)?;
}
write!(f, "")
}
}

90
src/display/structs.rs Normal file
View File

@ -0,0 +1,90 @@
use super::*;
// Display color
pub struct BgMapAttributes(pub u8);
impl BgMapAttributes {
pub fn palette_number(&self) -> usize {
(self.0 & 0b111) as usize
}
pub fn vram_bank_number(&self) -> usize {
((self.0 >> 3) & 1) as usize
}
pub fn horizontal_flip(&self) -> bool {
((self.0 >> 5) & 1) == 1
}
pub fn vertical_flip(&self) -> bool {
((self.0 >> 6) & 1) == 1
}
pub fn has_priority(&self) -> bool {
(self.0 >> 7) != 0
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum PixelOrigin {
Empty,
Background(usize),
BackgroundPriority,
Window,
Sprite,
}
pub struct Sprite {
pub x: u8,
pub y: u8,
pub tile: u8,
flags: u8,
}
impl Sprite {
pub fn load(buf: &[u8]) -> Self {
assert!(buf.len() >= 4);
Self {
x: buf[1],
y: buf[0],
tile: buf[2],
flags: buf[3],
}
}
pub fn is_hidden(&self) -> bool {
self.x == 0 || self.y == 0
}
pub fn is_foreground(&self) -> bool {
(self.flags & SPRITE_OBJ_BG_PRIORITY) == 0
}
pub fn is_x_flipped(&self) -> bool {
(self.flags & SPRITE_X_FLIP) == SPRITE_X_FLIP
}
pub fn is_y_flipped(&self) -> bool {
(self.flags & SPRITE_Y_FLIP) == SPRITE_Y_FLIP
}
pub fn palette(&self) -> u8 {
// GB
/*
if (self.flags & SPRITE_PALETTE_NO) == 0 {
0
} else {
1
}
*/
// GBC
self.flags & 0b111
}
// GBC only
pub fn vram_bank(&self) -> u8 {
if (self.flags & SPRITE_TILE_VRAM_BANK) == 0 {
0
} else {
1
}
}
}

View File

@ -32,6 +32,12 @@ extern crate sdl2;
use self::sdl2::event::Event; use self::sdl2::event::Event;
use self::sdl2::keyboard::Keycode; use self::sdl2::keyboard::Keycode;
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum TickResult {
Continue,
Shutdown,
}
#[derive(Debug)] #[derive(Debug)]
enum Key { enum Key {
UP, UP,
@ -50,7 +56,7 @@ pub struct Interconnect {
ram: Box<[u8]>, ram: Box<[u8]>,
hiram: Box<[u8]>, hiram: Box<[u8]>,
wram_bank: u8, wram_bank: u8,
sound: sound::Sound, sound: sound::SoundManager,
display: display::Display, display: display::Display,
interrupt: u8, interrupt: u8,
interrupt_request_flags: u8, interrupt_request_flags: u8,
@ -76,12 +82,12 @@ pub struct Interconnect {
impl Interconnect { impl Interconnect {
pub fn new(bios: Box<[u8]>, rom: Box<[u8]>, save_file: Option<String>) -> Interconnect { pub fn new(bios: Box<[u8]>, rom: Box<[u8]>, save_file: Option<String>) -> Interconnect {
Interconnect { Interconnect {
bios: bios, bios,
cartridge: cartridge::Cartridge::new(rom, save_file), cartridge: cartridge::Cartridge::new(rom, save_file),
ram: vec![0; WRAM_SIZE].into_boxed_slice(), ram: vec![0; WRAM_SIZE].into_boxed_slice(),
hiram: vec![0; HIRAM_SIZE].into_boxed_slice(), hiram: vec![0; HIRAM_SIZE].into_boxed_slice(),
wram_bank: 1, wram_bank: 1,
sound: sound::Sound::new(), sound: sound::SoundManager::new(),
display: display::Display::new(), display: display::Display::new(),
// Refactor those // Refactor those
interrupt_request_flags: 0, interrupt_request_flags: 0,
@ -137,7 +143,7 @@ impl Interconnect {
} }
// Somehow we need different timers for this. // Somehow we need different timers for this.
pub fn tick(&mut self, cycles: u8) { pub fn tick(&mut self, cycles: u8) -> TickResult {
self.display.tick(cycles as u16); self.display.tick(cycles as u16);
self.timer.tick(cycles as u16); self.timer.tick(cycles as u16);
@ -159,113 +165,66 @@ impl Interconnect {
// Make sure the window is responsive: // Make sure the window is responsive:
if self.cycles > 500 { if self.cycles > 500 {
loop { while let Some(event) = self.display.event_pump.poll_event() {
if let Some(event) = self.display.event_pump.poll_event() { match event {
match event { Event::Quit { .. }
Event::Quit { .. } | Event::KeyDown {
| Event::KeyDown { keycode: Some(Keycode::Escape),
keycode: Some(Keycode::Escape), ..
.. } => {
} => { self.cartridge.save();
self.cartridge.save(); self.display.dump_vram();
panic!("TODO: Proper shutdown"); return TickResult::Shutdown;
}
Event::KeyDown {
keycode: Some(Keycode::Left),
..
} => self.press_key(Key::LEFT),
Event::KeyDown {
keycode: Some(Keycode::Down),
..
} => self.press_key(Key::DOWN),
Event::KeyDown {
keycode: Some(Keycode::Up),
..
} => self.press_key(Key::UP),
Event::KeyDown {
keycode: Some(Keycode::Right),
..
} => self.press_key(Key::RIGHT),
Event::KeyDown {
keycode: Some(Keycode::A),
..
} => self.press_key(Key::START),
Event::KeyDown {
keycode: Some(Keycode::S),
..
} => self.press_key(Key::SELECT),
Event::KeyDown {
keycode: Some(Keycode::Z),
..
} => self.press_key(Key::A),
Event::KeyDown {
keycode: Some(Keycode::X),
..
} => self.press_key(Key::B),
Event::KeyUp {
keycode: Some(Keycode::Left),
..
} => self.release_key(Key::LEFT),
Event::KeyUp {
keycode: Some(Keycode::Down),
..
} => self.release_key(Key::DOWN),
Event::KeyUp {
keycode: Some(Keycode::Up),
..
} => self.release_key(Key::UP),
Event::KeyUp {
keycode: Some(Keycode::Right),
..
} => self.release_key(Key::RIGHT),
Event::KeyUp {
keycode: Some(Keycode::A),
..
} => self.release_key(Key::START),
Event::KeyUp {
keycode: Some(Keycode::S),
..
} => self.release_key(Key::SELECT),
Event::KeyUp {
keycode: Some(Keycode::Z),
..
} => self.release_key(Key::A),
Event::KeyUp {
keycode: Some(Keycode::X),
..
} => self.release_key(Key::B),
_ => {}
} }
} else { Event::KeyDown { keycode: k, .. } => match k {
break; Some(Keycode::Left) => self.press_key(Key::LEFT),
Some(Keycode::Down) => self.press_key(Key::DOWN),
Some(Keycode::Up) => self.press_key(Key::UP),
Some(Keycode::Right) => self.press_key(Key::RIGHT),
Some(Keycode::A) => self.press_key(Key::START),
Some(Keycode::S) => self.press_key(Key::SELECT),
Some(Keycode::Z) => self.press_key(Key::A),
Some(Keycode::X) => self.press_key(Key::B),
_ => {}
},
Event::KeyUp { keycode: k, .. } => match k {
Some(Keycode::Left) => self.release_key(Key::LEFT),
Some(Keycode::Down) => self.release_key(Key::DOWN),
Some(Keycode::Up) => self.release_key(Key::UP),
Some(Keycode::Right) => self.release_key(Key::RIGHT),
Some(Keycode::A) => self.release_key(Key::START),
Some(Keycode::S) => self.release_key(Key::SELECT),
Some(Keycode::Z) => self.release_key(Key::A),
Some(Keycode::X) => self.release_key(Key::B),
_ => {}
},
_ => {}
} }
} }
self.cycles = 0; self.cycles = 0;
} else { } else {
self.cycles += cycles as u16; self.cycles += cycles as u16;
} }
TickResult::Continue
} }
#[inline]
pub fn is_boot_rom(&self) -> bool { pub fn is_boot_rom(&self) -> bool {
self.disable_bootrom == 0 self.disable_bootrom == 0
} }
#[inline]
pub fn read_byte(&self, addr: u16) -> u8 { pub fn read_byte(&self, addr: u16) -> u8 {
// TODO: Make this more beautiful. // TODO: Make this more beautiful.
// TODO: if some flag set, use bios, otherwise only use rom // TODO: if some flag set, use bios, otherwise only use rom
// For now, just use bios // For now, just use bios
match addr { match addr {
0x0000..=0x100 => { 0x0000..=0x0FF | 0x200..=0x8FF => {
if self.disable_bootrom == 0 { if self.disable_bootrom == 0 && self.bios.len() > addr as usize {
self.bios[addr as usize] self.bios[addr as usize]
} else { } else {
self.cartridge.read_byte(addr) self.cartridge.read_byte(addr)
} }
} }
0x100..=0x7FFF => self.cartridge.read_byte(addr), 0x100..=0x1FF | 0x900..=0x7FFF => self.cartridge.read_byte(addr),
0x8000..=0x9FFF => self.display.read_byte(addr), 0x8000..=0x9FFF => self.display.read_byte(addr),
0xA000..=0xBFFF => self.cartridge.read_byte(addr), 0xA000..=0xBFFF => self.cartridge.read_byte(addr),
0xC000..=0xCFFF => self.ram[(addr - 0xC000) as usize], 0xC000..=0xCFFF => self.ram[(addr - 0xC000) as usize],
@ -289,9 +248,10 @@ impl Interconnect {
// println!("Reading IF: {:02X}", self.interrupt_request_flags); // println!("Reading IF: {:02X}", self.interrupt_request_flags);
self.interrupt_request_flags self.interrupt_request_flags
} }
0xFF10..=0xFF26 => self.sound.read_byte(addr), 0xFF10..=0xFF26 => self.sound.sound_object.lock().unwrap().read_byte(addr),
0xFF30..=0xFF3F => self.sound.read_byte(addr), 0xFF30..=0xFF3F => self.sound.sound_object.lock().unwrap().read_byte(addr),
0xFF40..=0xFF4B => self.display.read_byte(addr), 0xFF40..=0xFF4B => self.display.read_byte(addr),
0xFF4F => self.display.read_byte(addr),
0xFF50 => self.disable_bootrom, 0xFF50 => self.disable_bootrom,
0xFF51 => self.vram_dma_source_high, 0xFF51 => self.vram_dma_source_high,
0xFF52 => self.vram_dma_source_low, 0xFF52 => self.vram_dma_source_low,
@ -299,6 +259,7 @@ impl Interconnect {
0xFF54 => self.vram_dma_destination_low, 0xFF54 => self.vram_dma_destination_low,
0xFF55 => self.vram_dma_length, 0xFF55 => self.vram_dma_length,
0xFF56 => self.infrared_com_port, 0xFF56 => self.infrared_com_port,
0xFF6A | 0xFF6B => self.display.read_byte(addr),
0xFF70 => self.wram_bank, 0xFF70 => self.wram_bank,
0xFF80..=0xFFFE => self.hiram[(addr - 0xFF80) as usize], 0xFF80..=0xFFFE => self.hiram[(addr - 0xFF80) as usize],
0xFFFF => self.interrupt, 0xFFFF => self.interrupt,
@ -309,7 +270,6 @@ impl Interconnect {
} }
} }
#[inline]
pub fn write_byte(&mut self, addr: u16, val: u8) { pub fn write_byte(&mut self, addr: u16, val: u8) {
// TODO: Make this more beautful // TODO: Make this more beautful
/* /*
@ -335,6 +295,9 @@ impl Interconnect {
0xD000..=0xDFFF => { 0xD000..=0xDFFF => {
self.ram[(addr - 0xD000) as usize + self.wram_bank as usize * 0x1000] = val; self.ram[(addr - 0xD000) as usize + self.wram_bank as usize * 0x1000] = val;
} }
0xE000..=0xFCFF => {
self.ram[(addr - 0xE000) as usize] = val;
}
0xFE00..=0xFE9F => self.display.write_byte(addr, val), // OAM 0xFE00..=0xFE9F => self.display.write_byte(addr, val), // OAM
0xFF00 => { 0xFF00 => {
// Joystick select // Joystick select
@ -346,9 +309,18 @@ impl Interconnect {
self.interrupt_request_flags = val; self.interrupt_request_flags = val;
} }
0xFF10..=0xFF26 => { 0xFF10..=0xFF26 => {
self.sound.write_byte(addr, val); self.sound
.sound_object
.lock()
.expect("Sound-related crash")
.write_byte(addr, val);
} }
0xFF30..=0xFF3F => self.sound.write_byte(addr, val), 0xFF30..=0xFF3F => self
.sound
.sound_object
.lock()
.expect("Sound related crash")
.write_byte(addr, val),
// Exclude DMA transfer, we will do this below // Exclude DMA transfer, we will do this below
0xFF40..=0xFF45 | 0xFF47..=0xFF4B => { 0xFF40..=0xFF45 | 0xFF47..=0xFF4B => {
self.display.write_byte(addr, val); self.display.write_byte(addr, val);
@ -360,6 +332,7 @@ impl Interconnect {
self.write_byte(0xFE00 | x, dma_b); self.write_byte(0xFE00 | x, dma_b);
} }
} }
0xFF4F => self.display.write_byte(addr, val),
0xFF50 => { 0xFF50 => {
println!("Disabling boot rom."); println!("Disabling boot rom.");
self.disable_bootrom = val; self.disable_bootrom = val;
@ -374,28 +347,32 @@ impl Interconnect {
let mut dst: u16 = ((self.vram_dma_destination_high as u16) << 8) let mut dst: u16 = ((self.vram_dma_destination_high as u16) << 8)
| self.vram_dma_destination_low as u16; | self.vram_dma_destination_low as u16;
dst &= 0b0001_1111_1111_0000;
dst += 0x8000; dst += 0x8000;
println!( println!(
"VRAM DMA transfer from {:04X} to {:04X}; {:02X}", "VRAM DMA transfer from {:04X} to {:04X}; {:02X}",
src, dst, val src, dst, val
); );
let len: u16 = ((val & 0x7F) + 1) as u16 * 0x10 - 1; let len: u16 = ((val & 0x7F) + 1) as u16 * 0x10;
let _mode = val & 0x80 != 0;
for i in 0..len { for i in 0..len {
let v = self.read_byte(src.wrapping_add(i)); let v = self.read_byte(src.wrapping_add(i));
self.write_byte(dst.wrapping_add(i), v); self.write_byte(dst.wrapping_add(i), v);
} }
// DMA done // DMA done
self.vram_dma_length = val | 0x80; self.vram_dma_length = 0xFF; // val | 0x80;
} }
0xFF56 => { 0xFF56 => {
self.infrared_com_port = val; self.infrared_com_port = val;
} }
0xFF70 => { 0xFF70 => {
if self.wram_bank != val { if self.wram_bank != val {
println!("Switching wram bank to {:02X}", val);
if val > 7 { if val > 7 {
panic!("R u sure this is correct?"); panic!(
"Trying to switch to wram bank {} which is non-existing",
val
);
} }
if val == 0 { if val == 0 {
self.wram_bank = 1; self.wram_bank = 1;
@ -404,6 +381,8 @@ impl Interconnect {
} }
} }
} }
0xFF68 | 0xFF69 => self.display.write_byte(addr, val),
0xFF6A | 0xFF6B => self.display.write_byte(addr, val),
0xFF80..=0xFFFE => { 0xFF80..=0xFFFE => {
self.hiram[(addr - 0xFF80) as usize] = val; self.hiram[(addr - 0xFF80) as usize] = val;
} }
@ -417,13 +396,11 @@ impl Interconnect {
} }
} }
#[inline]
pub fn write_word(&mut self, addr: u16, val: u16) { pub fn write_word(&mut self, addr: u16, val: u16) {
self.write_byte(addr, val as u8); self.write_byte(addr, val as u8);
self.write_byte(addr + 1, (val >> 8) as u8); self.write_byte(addr + 1, (val >> 8) as u8);
} }
#[inline]
pub fn read_word(&self, addr: u16) -> u16 { pub fn read_word(&self, addr: u16) -> u16 {
self.read_byte(addr) as u16 | (self.read_byte(addr + 1) as u16) << 8 self.read_byte(addr) as u16 | (self.read_byte(addr + 1) as u16) << 8
// (self.read_byte(addr) as u16) << 8 | (self.read_byte(addr + 1) as u16) // (self.read_byte(addr) as u16) << 8 | (self.read_byte(addr + 1) as u16)

View File

@ -1,5 +1,7 @@
// let's try to write our own, awesome emulator. // let's try to write our own, awesome emulator.
// gameboy (color?) // gameboy (color?)
// To make things more readable at points
#![allow(clippy::identity_op)]
use std::env; use std::env;
use std::fs; use std::fs;
@ -24,10 +26,11 @@ fn main() {
} else { } else {
let bios_path = &args[1]; let bios_path = &args[1];
let rom_path = &args[2]; let rom_path = &args[2];
let mut save_file: Option<String> = None; let save_file = if args.len() == 4 {
if args.len() == 4 { Some(args[3].clone())
save_file = Some(args[3].clone()); } else {
} None
};
let bios = read_file(&bios_path).unwrap(); let bios = read_file(&bios_path).unwrap();
let rom = read_file(&rom_path).unwrap(); let rom = read_file(&rom_path).unwrap();
@ -36,21 +39,19 @@ fn main() {
let interconnect = interconnect::Interconnect::new(bios, rom, save_file); let interconnect = interconnect::Interconnect::new(bios, rom, save_file);
let mut cpu = cpu::CPU::new(interconnect); let mut cpu = cpu::CPU::new(interconnect);
loop { cpu.run();
cpu.run();
}
} }
} }
pub fn read_file<P: AsRef<Path>>(rom_path: P) -> Result<Box<[u8]>, io::Error> { pub fn read_file<P: AsRef<Path>>(rom_path: P) -> Result<Box<[u8]>, io::Error> {
let mut file = r#try!(fs::File::open(rom_path)); let mut file = fs::File::open(rom_path)?;
let mut buf = Vec::new(); let mut buf = Vec::new();
r#try!(file.read_to_end(&mut buf)); file.read_to_end(&mut buf)?;
Ok(buf.into_boxed_slice()) Ok(buf.into_boxed_slice())
} }
pub fn write_file<P: AsRef<Path>>(path: P, data: &Box<[u8]>) -> Result<(), io::Error> { pub fn write_file<P: AsRef<Path>>(path: P, data: &[u8]) -> Result<(), io::Error> {
let mut file = r#try!(fs::File::create(path)); let mut file = fs::File::create(path)?;
r#try!(file.write(&data)); file.write_all(&data)?;
Ok(()) Ok(())
} }

View File

@ -1,53 +0,0 @@
pub trait MBC {
fn read_byte(&self, addr: u16) -> u8;
fn write_byte(&mut self, addr: u16, val: u8);
fn dump_ram(&self, file: &String);
}
pub struct NoMBC {
rom: Box<[u8]>,
ram: Box<[u8]>,
}
impl NoMBC {
pub fn new(rom: Box<[u8]>, ram: Box<[u8]>) -> NoMBC {
NoMBC { rom: rom, ram: ram }
}
}
impl MBC for NoMBC {
fn dump_ram(&self, file: &String) {
super::super::write_file(&file, &self.ram).expect("Saving failed");
}
fn write_byte(&mut self, addr: u16, val: u8) {
println!(
"Writing not supported for cartridges without MBC. (Tried to set {:04X} to {:02X})",
addr, val
);
}
fn read_byte(&self, addr: u16) -> u8 {
match addr {
0x0000..=0x7FFF => self.rom[addr as usize],
0xA000..=0xBFFF => {
// TODO: Check for ram
let addr = (addr as usize) - 0xA000;
if addr >= self.ram.len() {
println!(
"Tried to access {:04X}, however the memory is not present.",
addr + 0xA000
);
0
} else {
self.ram[addr]
}
}
_ => {
panic!("Cartride: Unable to read from {:04X}", addr);
}
}
}
}

View File

@ -1,4 +1,4 @@
use super::mbc::MBC; use super::MBC;
enum BankMode { enum BankMode {
RomBankMode, RomBankMode,
@ -17,8 +17,8 @@ pub struct MBC1 {
impl MBC1 { impl MBC1 {
pub fn new(rom: Box<[u8]>, ram: Box<[u8]>) -> MBC1 { pub fn new(rom: Box<[u8]>, ram: Box<[u8]>) -> MBC1 {
MBC1 { MBC1 {
rom: rom, rom,
ram: ram, ram,
rom_bank_no: 1, rom_bank_no: 1,
bank_mode: BankMode::RomBankMode, bank_mode: BankMode::RomBankMode,
bank_no_high: 0, bank_no_high: 0,
@ -43,7 +43,7 @@ impl MBC1 {
} }
impl MBC for MBC1 { impl MBC for MBC1 {
fn dump_ram(&self, file: &String) { fn dump_ram(&self, file: &str) {
super::super::write_file(&file, &self.ram).expect("Saving failed"); super::super::write_file(&file, &self.ram).expect("Saving failed");
} }
@ -94,6 +94,11 @@ impl MBC for MBC1 {
_ => panic!("Invalid bank mode {:02X}", val), _ => panic!("Invalid bank mode {:02X}", val),
} }
} }
0xA000..=0xBFFF => {
let addr = addr - 0xA000;
println!("Access [{:02X}] {:04X}", self.active_ram_bank(), addr);
self.ram[self.active_ram_bank() as usize * 0x2000 + addr as usize] = val;
}
_ => panic!("MBC1: Writing {:02X} to {:04X} not supported", val, addr), _ => panic!("MBC1: Writing {:02X} to {:04X} not supported", val, addr),
} }
} }

View File

@ -1,4 +1,4 @@
use super::mbc::MBC; use super::MBC;
pub struct MBC2 { pub struct MBC2 {
rom: Box<[u8]>, rom: Box<[u8]>,
@ -10,8 +10,8 @@ pub struct MBC2 {
impl MBC2 { impl MBC2 {
pub fn new(rom: Box<[u8]>, ram: Box<[u8]>) -> MBC2 { pub fn new(rom: Box<[u8]>, ram: Box<[u8]>) -> MBC2 {
MBC2 { MBC2 {
rom: rom, rom,
ram: ram, ram,
rom_bank_no: 1, rom_bank_no: 1,
ram_enable: false, ram_enable: false,
} }
@ -23,7 +23,7 @@ impl MBC2 {
} }
impl MBC for MBC2 { impl MBC for MBC2 {
fn dump_ram(&self, file: &String) { fn dump_ram(&self, file: &str) {
super::super::write_file(&file, &self.ram).expect("Saving failed"); super::super::write_file(&file, &self.ram).expect("Saving failed");
} }
@ -61,7 +61,7 @@ impl MBC for MBC2 {
} }
} }
0x2000..=0x3FFF => { 0x2000..=0x3FFF => {
if addr & 0x0100 == 1 { if addr & 0x0100 == 0 {
self.rom_bank_no = val & 0x0F; self.rom_bank_no = val & 0x0F;
println!("MBC2: Selecting bank {:02X}", self.rom_bank_no); println!("MBC2: Selecting bank {:02X}", self.rom_bank_no);
} else { } else {

View File

@ -1,4 +1,4 @@
use super::mbc::MBC; use super::MBC;
pub struct MBC3 { pub struct MBC3 {
rom: Box<[u8]>, rom: Box<[u8]>,
@ -11,8 +11,8 @@ pub struct MBC3 {
impl MBC3 { impl MBC3 {
pub fn new(rom: Box<[u8]>, ram: Box<[u8]>) -> MBC3 { pub fn new(rom: Box<[u8]>, ram: Box<[u8]>) -> MBC3 {
MBC3 { MBC3 {
rom: rom, rom,
ram: ram, ram,
rom_bank_no: 1, rom_bank_no: 1,
ram_bank_no: 0, ram_bank_no: 0,
ram_rtc_enabled: false, ram_rtc_enabled: false,
@ -28,7 +28,7 @@ impl MBC3 {
} }
impl MBC for MBC3 { impl MBC for MBC3 {
fn dump_ram(&self, file: &String) { fn dump_ram(&self, file: &str) {
super::super::write_file(&file, &self.ram).expect("Saving failed"); super::super::write_file(&file, &self.ram).expect("Saving failed");
} }

115
src/mbc/mbc5.rs Normal file
View File

@ -0,0 +1,115 @@
use super::MBC;
pub struct MBC5 {
rom: Box<[u8]>,
ram: Box<[u8]>,
rom_bank_no: u16,
ram_bank_no: u8,
ram_rtc_enabled: bool,
}
impl MBC5 {
pub fn new(rom: Box<[u8]>, ram: Box<[u8]>) -> Self {
MBC5 {
rom,
ram,
rom_bank_no: 1,
ram_bank_no: 0,
ram_rtc_enabled: false,
}
}
fn active_rom_bank(&self) -> u16 {
self.rom_bank_no
}
fn active_ram_bank(&self) -> u8 {
self.ram_bank_no
}
}
impl MBC for MBC5 {
fn dump_ram(&self, file: &str) {
use crate::write_file;
write_file(&file, &self.ram).expect("Saving failed");
}
fn read_byte(&self, addr: u16) -> u8 {
match addr {
0x0000..=0x3FFF => self.rom[addr as usize],
0x4000..=0x7FFF => {
let addr = addr - 0x4000;
let abs_addr: usize = addr as usize + self.active_rom_bank() as usize * 0x4000;
let val: u8 = self.rom[abs_addr];
val
}
0xA000..=0xBFFF => {
let addr = addr - 0xA000;
match self.active_ram_bank() {
0x00..=0x03 => {
self.ram[self.active_ram_bank() as usize * 0x2000 + addr as usize]
}
0x08..=0x0C => {
// TODO
println!("MBC5: Ignoring RTC read");
0
}
_ => panic!(
"MBC5: Accessing unknown RAM bank {:02X}",
self.active_ram_bank()
),
}
}
_ => {
panic!("MBC5: Unable to read from {:04X}", addr);
}
}
}
fn write_byte(&mut self, addr: u16, val: u8) {
match addr {
0x0000..=0x1FFF => match val & 0x0F {
0x0A => self.ram_rtc_enabled = true,
0x00 => self.ram_rtc_enabled = false,
_ => {
println!("MBC5: Unknown MBC value {:02X} for {:04X}", val, addr);
self.ram_rtc_enabled = false;
}
},
// Lower part rom bank select
0x2000..=0x2FFF => self.rom_bank_no = (self.rom_bank_no & (!0xFF)) | u16::from(val),
0x3000..=0x3FFF => {
self.rom_bank_no = (self.rom_bank_no & (0xFF)) | ((u16::from(val) & 1) << 8)
}
0x4000..=0x5FFF => {
// RAM bank select
self.ram_bank_no = val & 0x0F;
}
0x6000..=0x7FFF => {
// Latch clock data
match val {
0x00 => println!("latch = 0"),
0x01 => println!("latch = 1"), // TODO: This should copy the current clock to the register
_ => panic!("MBC5: Unknown latch value {:02X}", val),
}
}
0xA000..=0xBFFF => {
let addr = addr - 0xA000;
match self.active_ram_bank() {
0x00..=0x03 => {
let active_bank = self.active_ram_bank() as usize;
self.ram[active_bank * 0x2000 + addr as usize] = val;
}
0x08..=0x0C => {
// TODO
println!("MBC5: Ignoring RTC write ({:02X})", val);
}
_ => panic!(
"MBC5: Writing unknown RAM bank {:02X}",
self.active_ram_bank()
),
}
}
_ => panic!("MBC5: Writing {:02X} to {:04X} not supported", val, addr),
}
}
}

View File

@ -1,4 +1,63 @@
pub mod mbc; mod mbc1;
pub mod mbc1; mod mbc2;
pub mod mbc2; mod mbc3;
pub mod mbc3; mod mbc5;
pub use mbc1::MBC1;
pub use mbc2::MBC2;
pub use mbc3::MBC3;
pub use mbc5::MBC5;
pub trait MBC {
fn read_byte(&self, addr: u16) -> u8;
fn write_byte(&mut self, addr: u16, val: u8);
fn dump_ram(&self, file: &str);
}
pub struct NoMBC {
rom: Box<[u8]>,
ram: Box<[u8]>,
}
impl NoMBC {
pub fn new(rom: Box<[u8]>, ram: Box<[u8]>) -> NoMBC {
NoMBC { rom, ram }
}
}
impl MBC for NoMBC {
fn dump_ram(&self, file: &str) {
super::write_file(&file, &self.ram).expect("Saving failed");
}
fn write_byte(&mut self, addr: u16, val: u8) {
println!(
"Writing not supported for cartridges without MBC. (Tried to set {:04X} to {:02X})",
addr, val
);
}
fn read_byte(&self, addr: u16) -> u8 {
match addr {
0x0000..=0x7FFF => self.rom[addr as usize],
0xA000..=0xBFFF => {
// TODO: Check for ram
let addr = (addr as usize) - 0xA000;
if addr >= self.ram.len() {
println!(
"Tried to access {:04X}, however the memory is not present.",
addr + 0xA000
);
0
} else {
self.ram[addr]
}
}
_ => {
panic!("Cartride: Unable to read from {:04X}", addr);
}
}
}
}

View File

@ -12,7 +12,7 @@ impl Serial {
pub fn write_byte(&mut self, addr: u16, val: u8) { pub fn write_byte(&mut self, addr: u16, val: u8) {
match addr { match addr {
0xFF01 => { 0xFF01 => {
println!("Serial: Write {:02X}", val); // println!("Serial: Write {:02X}", val);
self.data = val; self.data = val;
} }
0xFF02 => self.control = val, 0xFF02 => self.control = val,

View File

@ -1,61 +0,0 @@
#[derive(Debug, Default)]
pub struct Sound {
enabled: u8,
sound_length_1: u8,
sound_control_1: u8,
sound_channel_volume_control: u8,
sound_output_terminal_selector: u8,
sound_freq_low: u8,
sound_freq_high: u8,
channel2_sound_length: u8,
channel2_volume: u8,
channel2_freq_lo: u8,
channel2_freq_hi: u8,
}
impl Sound {
pub fn new() -> Sound {
Sound::default()
}
pub fn write_byte(&mut self, addr: u16, val: u8) {
match addr {
0xFF11 => self.sound_length_1 = val,
0xFF12 => self.sound_control_1 = val,
0xFF13 => self.sound_freq_low = val,
0xFF14 => self.sound_freq_high = val,
0xFF16 => self.channel2_sound_length = val,
0xFF17 => self.channel2_volume = val,
0xFF18 => self.channel2_freq_lo = val,
0xFF19 => self.channel2_freq_hi = val,
0xFF24 => self.sound_channel_volume_control = val,
0xFF25 => self.sound_output_terminal_selector = val,
0xFF26 => self.enabled = val,
_ => {
// println!("Sound: Write {:02X} to {:04X} unsupported", val, addr);
}
}
}
pub fn read_byte(&self, addr: u16) -> u8 {
match addr {
0xFF11 => self.sound_length_1,
0xFF12 => self.sound_control_1,
0xFF13 => self.sound_freq_low,
0xFF14 => self.sound_freq_high,
0xFF16 => self.channel2_sound_length,
0xFF17 => self.channel2_volume,
0xFF18 => self.channel2_freq_lo,
0xFF19 => self.channel2_freq_hi,
0xFF24 => self.sound_channel_volume_control,
0xFF25 => self.sound_output_terminal_selector,
0xFF26 => self.enabled,
_ => panic!("Sound: Read from {:04X} unsupported", addr),
}
}
}

109
src/sound/envelope.rs Normal file
View File

@ -0,0 +1,109 @@
// Implements the envelopes
use crate::sound::{AudioComponent, AudioModule};
#[derive(Clone, Debug)]
pub enum EnvelopeDirection {
Increase,
Decrease,
}
impl Default for EnvelopeDirection {
fn default() -> Self {
EnvelopeDirection::Decrease
}
}
/// Represents a volume envelope for the sound channels
#[derive(Default, Debug)]
pub struct VolumeEnvelope {
/// Accessible via the corresponding memory location, used as template
/// when the sound is enabled
register_value: u8,
/// Amount of clocks required for one step
clocks_per_step: u8,
/// Clocks occured since last step
clocks: u8,
/// Current volume
current_volume: u8,
/// Envelope direction
direction: EnvelopeDirection,
}
#[allow(dead_code)]
impl VolumeEnvelope {
/// Returns the intiial volume of an envelope operation
pub fn initial_volume(&self) -> u8 {
self.register_value >> 4
}
/// Returns the envelope direction
pub fn envelope_direction(&self) -> EnvelopeDirection {
match self.register_value & (1 << 3) {
0 => EnvelopeDirection::Decrease,
_ => EnvelopeDirection::Increase,
}
}
/// Return the number of sweep steps
pub fn envelope_sweep_count(&self) -> u8 {
self.register_value & 7
}
pub fn start(&mut self) {
self.current_volume = self.initial_volume();
self.clocks_per_step = self.envelope_sweep_count();
self.direction = self.envelope_direction();
self.clocks = 0;
}
pub fn set_register(&mut self, register: u8) {
self.register_value = register; // & !(1 << 6); // TODO: Where does this come from?
if register & (1 << 6) != 0 {
self.start();
}
}
}
impl AudioModule<u8> for VolumeEnvelope {
fn transform(&self, sample: u8) -> u8 {
if sample > 0 {
128 + self.current_volume
} else {
128 - self.current_volume
}
}
}
impl AudioComponent for VolumeEnvelope {
fn clock(&mut self) {
if self.clocks_per_step > 0 {
self.clocks += 1;
if self.clocks == self.clocks_per_step {
self.clocks = 0;
self.current_volume = match self.direction {
EnvelopeDirection::Decrease => {
if self.current_volume > 0 {
self.current_volume - 1
} else {
self.clocks_per_step = 0;
0
}
}
EnvelopeDirection::Increase => {
if self.current_volume < 15 {
self.current_volume + 1
} else {
self.clocks_per_step = 0;
15
}
}
};
}
}
}
}

72
src/sound/length.rs Normal file
View File

@ -0,0 +1,72 @@
// Implement the length counter
use crate::sound::{AudioComponent, AudioModule};
use std::fmt::Debug;
#[derive(Default, Debug)]
pub struct LengthCounter<T>
where
T: Default + Debug,
{
// Do we need an additional enabled flag?
counter: T,
enabled: bool,
}
impl<T> LengthCounter<T>
where
T: Default + Debug,
{
pub fn set_length(&mut self, value: T) {
self.counter = value;
}
pub fn set_enabled(&mut self, val: u8) {
self.enabled = val > 0;
}
}
impl AudioModule<u8> for LengthCounter<u16> {
fn transform(&self, sample: u8) -> u8 {
if self.enabled {
if self.counter > 0 {
sample
} else {
0
}
} else {
sample
}
}
}
impl AudioComponent for LengthCounter<u16> {
fn clock(&mut self) {
if self.enabled && self.counter > 0 {
self.counter -= 1;
}
}
}
// TODO: Is the same as above, there is something we should be able to do about
// this
impl AudioModule<u8> for LengthCounter<u8> {
fn transform(&self, sample: u8) -> u8 {
if self.enabled {
if self.counter > 0 {
sample
} else {
0
}
} else {
sample
}
}
}
impl AudioComponent for LengthCounter<u8> {
fn clock(&mut self) {
if self.enabled && self.counter > 0 {
self.counter -= 1;
}
}
}

547
src/sound/mod.rs Normal file
View File

@ -0,0 +1,547 @@
#[cfg(feature = "audio")]
extern crate pulse_simple;
mod envelope;
mod length;
mod square;
mod wave;
#[cfg(feature = "audio")]
use self::pulse_simple::Playback;
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc, Mutex,
};
#[cfg(feature = "audio")]
use std::thread;
use std::convert::TryFrom;
const OUTPUT_SAMPLE_RATE: usize = 48100;
//const OUTPUT_SAMPLE_RATE: usize = 32768;
/// Represents an 'audio module', transforming an input sample to an output sample
trait AudioModule<T> {
fn transform(&self, sample: T) -> T;
}
/// Clockable audio components
trait AudioComponent {
fn clock(&mut self);
}
#[derive(Debug, Default)]
struct Channel1 {
// Square 1: Sweep -> Timer -> Duty -> Length Counter -> Envelope -> Mixer
wave_gen: square::SquareWaveGenerator,
length_counter: length::LengthCounter<u8>,
envelope: envelope::VolumeEnvelope,
tick_state: u8,
stored_regs: [u8; 5],
}
impl Channel1 {
pub fn write_byte(&mut self, addr: u16, val: u8) {
match addr {
0xFF10 => {
self.wave_gen.set_sweep_reg(val);
self.stored_regs[0] = val;
}
0xFF11 => {
self.length_counter.set_length(val & 0x3F);
self.wave_gen.set_duty_cycle(val >> 6);
self.stored_regs[1] = val;
}
0xFF12 => {
self.envelope.set_register(val);
self.stored_regs[2] = val;
}
0xFF13 => {
// Set lower frequency
self.wave_gen.set_lower_freq(val);
self.stored_regs[3] = val;
}
0xFF14 => {
self.stored_regs[4] = val;
// Set higher
self.wave_gen.set_higher_freq(val & 0b111);
if val & (1 << 7) != 0 {
self.wave_gen.reset();
self.envelope.start();
}
self.length_counter.set_enabled(val & 1 << 6);
}
_ => panic!("Channel1: Write not supported: {:04X} = {:02X}", addr, val),
}
}
pub fn read_byte(&self, addr: u16) -> u8 {
match addr {
0xFF10..=0xFF14 => self.stored_regs[(addr - 0xFF10) as usize],
_ => {
println!("Channel1 does not support reading ({:04X})", addr);
0
}
}
}
pub fn sample_clock(&mut self) {
self.wave_gen.clock();
}
pub fn sample(&self) -> u8 {
let input = self.wave_gen.sample();
// Follow the chain
let input = self.length_counter.transform(input);
self.envelope.transform(input)
}
pub fn clock(&mut self) {
if self.tick_state % 2 == 0 {
self.length_counter.clock();
}
if self.tick_state == 2 || self.tick_state == 6 {
self.wave_gen.sweep_clock();
}
if self.tick_state == 7 {
self.envelope.clock();
}
// TODO: Sweep
// TODO: Sweep in adition mode + overflow? stop.
self.tick_state += 1;
if self.tick_state == 8 {
self.tick_state = 0;
}
}
}
#[derive(Debug, Default)]
struct Channel2 {
// Square 2: Timer -> Duty -> Length Counter -> Envelope -> Mixer
wave_gen: square::SquareWaveGenerator,
length_counter: length::LengthCounter<u8>,
envelope: envelope::VolumeEnvelope,
tick_state: u8,
}
impl Channel2 {
pub fn write_byte(&mut self, addr: u16, val: u8) {
match addr {
0xFF16 => {
self.length_counter.set_length(val & 0x3F);
self.wave_gen.set_duty_cycle(val >> 6);
}
0xFF17 => {
self.envelope.set_register(val);
}
0xFF18 => {
// Set lower frequency
self.wave_gen.set_lower_freq(val);
}
0xFF19 => {
// Set higher
self.wave_gen.set_higher_freq(val & 0b111);
if val & (1 << 7) != 0 {
self.wave_gen.reset();
self.envelope.start();
}
self.length_counter.set_enabled(val & 1 << 6);
}
_ => panic!("Channel2: Write not supported: {:04X} = {:02X}", addr, val),
}
}
pub fn read_byte(&self, addr: u16) -> u8 {
use crate::sound::square::DutyCycle::*;
match addr {
0xFF16 => match self.wave_gen.duty_cycle {
Duty0 => 0, // 12.5%
Duty1 => 0b0100_0000, // 25.0%
Duty2 => 0b1000_0000, // 50.0%
Duty3 => 0b1100_0000, // 75.0%
},
_ => {
println!("Channel2 does not support reading ({:04X})", addr);
0
}
}
}
pub fn sample_clock(&mut self) {
self.wave_gen.clock();
}
pub fn sample(&self) -> u8 {
let input = self.wave_gen.sample();
// Follow the chain
let input = self.length_counter.transform(input);
self.envelope.transform(input)
}
pub fn clock(&mut self) {
if self.tick_state % 2 == 0 {
self.length_counter.clock();
}
if self.tick_state == 2 || self.tick_state == 6 {
// TODO: Sweep
}
if self.tick_state == 7 {
self.envelope.clock();
}
self.tick_state += 1;
if self.tick_state == 8 {
self.tick_state = 0;
}
}
}
#[derive(Debug)]
enum Channel3VolumeSetting {
Muted,
Original,
Half,
Quarter,
}
impl From<u8> for Channel3VolumeSetting {
fn from(o: u8) -> Self {
use Channel3VolumeSetting::*;
match o {
0 => Muted,
1 => Original,
2 => Half,
3 => Quarter,
_ => unreachable!("Invalid channel volume setting"),
}
}
}
impl Default for Channel3VolumeSetting {
fn default() -> Self {
Self::Muted
}
}
impl Channel3VolumeSetting {
fn transform(&self, sample: u8) -> u8 {
match self {
Self::Muted => 128,
// Actually making it louder as the wave src is quite silent.
Self::Original => ((sample - 120) << 1) + 112,
Self::Half => sample,
Self::Quarter => ((sample - 120) >> 1) + 124,
}
}
}
#[derive(Debug, Default)]
struct Channel3 {
// Timer -> Wave -> Length Counter -> Volume -> Mixer
wave_gen: wave::WaveGenerator,
length_counter: length::LengthCounter<u8>,
volume_ctrl: Channel3VolumeSetting,
tick_state: u8,
}
impl Channel3 {
pub fn write_byte(&mut self, addr: u16, val: u8) {
// OFF flag -> "bit 7 of NR30"
match addr {
0xFF1A => {
self.wave_gen.enabled = (val & (1 << 7)) > 0;
}
0xFF1B => {
self.length_counter.set_length(val);
}
0xFF1C => self.volume_ctrl = ((val >> 5) & 0b11).into(),
0xFF1D => {
// Set lower frequency
self.wave_gen.set_lower_freq(val);
}
0xFF1E => {
// Set higher
self.wave_gen.set_higher_freq(val & 0b111);
if val & (1 << 7) != 0 {
self.wave_gen.reset();
}
self.length_counter.set_enabled(val & (1 << 6));
}
0xFF30..=0xFF3F => {
self.wave_gen.set_sample_pair((addr - 0xFF30) as usize, val);
}
_ => panic!("Channel3: Write not supported: {:04X} = {:02X}", addr, val),
}
}
pub fn read_byte(&self, addr: u16) -> u8 {
match addr {
0xFF30..=0xFF3F => self.wave_gen.get_sample_pair((addr - 0xFF30) as usize),
_ => panic!("Channel3 does not support reading ({:04X})", addr),
}
}
pub fn sample_clock(&mut self) {
self.wave_gen.clock();
}
pub fn sample(&self) -> u8 {
let input = self.wave_gen.sample();
// TODO(?): Follow the chain
let input = self.length_counter.transform(input);
self.volume_ctrl.transform(input)
}
pub fn clock(&mut self) {
if self.tick_state % 2 == 0 {
self.length_counter.clock();
}
if self.tick_state == 2 || self.tick_state == 6 {
// TODO: Sweep
}
self.tick_state += 1;
if self.tick_state == 8 {
self.tick_state = 0;
}
}
}
#[derive(Default)]
pub struct Sound {
channel1: Channel1,
channel2: Channel2,
channel3: Channel3,
sound_channel_volume_control: u8,
sound_output_terminal_selector: u8,
enabled: u8,
}
pub struct SoundManager {
pub sound_object: Arc<Mutex<Sound>>,
handle: Option<std::thread::JoinHandle<()>>,
do_exit: Arc<AtomicBool>,
sound_enabled: bool,
}
impl Drop for SoundManager {
fn drop(&mut self) {
self.do_exit.store(true, Ordering::Relaxed);
self.handle.take().and_then(|h| h.join().ok());
}
}
impl SoundManager {
pub fn new() -> Self {
let mut res = SoundManager {
sound_object: Arc::new(Mutex::new(Sound::new())),
handle: None,
do_exit: Arc::new(AtomicBool::new(false)),
sound_enabled: true,
};
res.launch_thread();
res
}
fn launch_thread(&mut self) {
let obj = self.sound_object.clone();
let do_exit = self.do_exit.clone();
#[cfg(feature = "audio")]
{
if self.sound_enabled {
self.handle = Some(
thread::Builder::new()
.name("Audio".into())
.spawn(move || {
// PulseAudio playback object
let playback =
Playback::new("GBC", "Output", None, (OUTPUT_SAMPLE_RATE) as _);
// Counter, used for calling the 512 Hz timer
let mut counter = 0;
while !do_exit.load(Ordering::Relaxed) {
const N_SAMPLES: usize = 1024;
let mut sample_buffer = [[128u8; 2]; N_SAMPLES];
for idx in 0..N_SAMPLES {
if let Some((s1, s2)) = {
let mut c_obj = obj.lock().unwrap();
// Check for 512 Hz timer
if counter >= (OUTPUT_SAMPLE_RATE / 512) {
c_obj.clock();
counter = 0;
} else {
counter += 1;
}
// Sample clock
c_obj.sample_clock();
// Get sample
c_obj.sample()
} {
sample_buffer[idx] = [s1, s2];
}
}
playback.write(&sample_buffer[..]);
}
})
.unwrap(),
);
} else {
println!("Sound will be redirected to /dev/null ;)");
self.handle = None;
}
}
}
}
impl Sound {
pub fn new() -> Sound {
Sound::default()
}
pub fn clock(&mut self) {
self.channel1.clock();
self.channel2.clock();
self.channel3.clock();
}
pub fn sample_clock(&mut self) {
self.channel1.sample_clock();
self.channel2.sample_clock();
self.channel3.sample_clock();
}
pub fn sample(&self) -> Option<(u8, u8)> {
if self.enabled == 0 {
return None;
}
// Some basic mixing
// Output value
let mut s = (0, 0);
let s01_volume = self.sound_channel_volume_control & 7;
let s02_volume = (self.sound_channel_volume_control >> 4) & 7;
/*
if self.sound_channel_volume_control & 0b1000 != 0 {
VIN!
}
if self.sound_channel_volume_control & 0b1000_0000 != 0 {
VIN!
}
*/
let c1_sample = self.channel1.sample();
let c2_sample = self.channel2.sample();
let c3_sample = self.channel3.sample();
let c4_sample = 128;
if self.sound_output_terminal_selector & 1 > 0 {
// Output Channel1
s.0 += i16::try_from(c1_sample).unwrap() - 128;
}
if self.sound_output_terminal_selector & 2 > 0 {
// Output Channel2
s.0 += i16::try_from(c2_sample).unwrap() - 128;
}
if self.sound_output_terminal_selector & 4 > 0 {
// Output Channel3
s.0 += i16::try_from(c3_sample).unwrap() - 128;
}
if self.sound_output_terminal_selector & 8 > 0 {
// Output Channel4
s.0 += i16::try_from(c4_sample).unwrap() - 128;
}
if self.sound_output_terminal_selector & 0x10 > 0 {
// Output Channel1
s.1 += i16::try_from(c1_sample).unwrap() - 128;
}
if self.sound_output_terminal_selector & 0x20 > 0 {
// Output Channel2
s.1 += i16::try_from(c2_sample).unwrap() - 128;
}
if self.sound_output_terminal_selector & 0x40 > 0 {
// Output Channel3
s.1 += i16::try_from(c3_sample).unwrap() - 128;
}
if self.sound_output_terminal_selector & 0x80 > 0 {
// Output Channel4
s.1 += i16::try_from(c4_sample).unwrap() - 128;
}
// TODO: This is too loud (overflowing u8) for some reason, so we're
// backing off a little bit from this.
/*
if s.0 > 30 || s.1 > 30 {
println!("Would overflow! s={:?} s1vol={} s2vol={}", s, s01_volume, s02_volume);
}
*/
//s.0 *= i16::try_from(s01_volume).unwrap(); // + 1;
//s.1 *= i16::try_from(s02_volume).unwrap(); // + 1;
let s = (128 + s.0, 128 + s.1);
let s = (u8::try_from(s.0).unwrap(), u8::try_from(s.1).unwrap());
Some(s)
}
pub fn write_byte(&mut self, addr: u16, val: u8) {
// Bit 7 of NR52 = 0 -> everything reset
// If all sound off no mode register can be set
// println!("Snd: {:04X} = {:02X} ({:08b})", addr, val, val);
match addr {
0xFF10..=0xFF14 => self.channel1.write_byte(addr, val),
0xFF16..=0xFF19 => self.channel2.write_byte(addr, val),
0xFF1A..=0xFF1E => self.channel3.write_byte(addr, val),
// TODO: Channel 4
0xFF24 => self.sound_channel_volume_control = val,
0xFF25 => self.sound_output_terminal_selector = val,
0xFF26 => self.enabled = val,
0xFF30..=0xFF3F => self.channel3.write_byte(addr, val),
_ => {
println!("Sound: Write {:02X} to {:04X} unsupported", val, addr);
}
}
}
pub fn read_byte(&self, addr: u16) -> u8 {
// println!("RD {:04X}", addr);
match addr {
0xFF10..=0xFF14 => self.channel1.read_byte(addr),
0xFF16..=0xFF19 => self.channel2.read_byte(addr),
0xFF1A..=0xFF1E => self.channel3.read_byte(addr),
0xFF24 => self.sound_channel_volume_control,
0xFF25 => self.sound_output_terminal_selector,
0xFF26 => self.enabled,
0xFF30..=0xFF3F => self.channel3.read_byte(addr),
_ => panic!("Sound: Read from {:04X} unsupported", addr),
}
}
}

138
src/sound/square.rs Normal file
View File

@ -0,0 +1,138 @@
// Square wave generator
use crate::sound::AudioComponent;
use crate::sound::OUTPUT_SAMPLE_RATE;
#[derive(Debug)]
pub enum DutyCycle {
Duty0, // 12.5%
Duty1, // 25.0%
Duty2, // 50.0%
Duty3, // 75.0%
}
impl Default for DutyCycle {
fn default() -> Self {
DutyCycle::Duty2
}
}
#[derive(Debug, Default)]
pub struct SquareWaveGenerator {
frequency: f32,
time: f32,
pub duty_cycle: DutyCycle,
gb_reg_freq: u16,
// Sweep settings (if existing)
sweep_change_period: u8,
// selects the inc/dec mode of the sweep. Dec if set
sweep_dec: bool,
// Sweep speed (n/128th)
sweep_change: u8,
// Current sweep clock cycle
sweep_clock: u8,
// sweep_freq_shadow: f32,
}
impl SquareWaveGenerator {
fn update_frequency(&mut self) {
self.frequency = 131072f32 / ((2048 - self.gb_reg_freq) as f32);
}
pub fn set_lower_freq(&mut self, freq: u8) {
self.gb_reg_freq &= 0xFF00;
self.gb_reg_freq |= freq as u16;
self.update_frequency();
}
pub fn set_higher_freq(&mut self, freq: u8) {
self.gb_reg_freq &= 0x00FF;
self.gb_reg_freq |= ((freq & 7) as u16) << 8;
self.update_frequency();
}
pub fn set_sweep_reg(&mut self, reg: u8) {
//Bit 6-4 - Sweep Time
//Bit 3 - Sweep Increase/Decrease
// 0: Addition (frequency increases)
// 1: Subtraction (frequency decreases)
//Bit 2-0 - Number of sweep shift (n: 0-7)
self.sweep_change_period = (reg >> 4) & 0b0111;
self.sweep_dec = reg & (1 << 3) > 0;
self.sweep_change = reg & 0b111;
self.sweep_clock = 0;
}
pub fn sweep_clock(&mut self) {
// This should be sampled at 128Hz
if self.sweep_change_period > 0 // enabled
&& self.sweep_clock >= self.sweep_change_period
// sweep time hit
{
self.sweep_clock = 0;
let change = self.frequency / (2f32.powi(self.sweep_change as _));
self.frequency += if self.sweep_dec { -change } else { change };
}
}
pub fn reset(&mut self) {
self.time = 0f32;
}
pub fn sample(&self) -> u8 {
if self.frequency > 0f32 {
let mut temp = self.time * self.frequency;
temp -= temp.trunc();
match self.duty_cycle {
DutyCycle::Duty0 => {
if temp < 0.125 {
255
} else {
0
}
}
DutyCycle::Duty1 => {
if temp < 0.25 {
255
} else {
0
}
}
DutyCycle::Duty2 => {
if temp < 0.5 {
255
} else {
0
}
}
DutyCycle::Duty3 => {
if temp < 0.75 {
255
} else {
0
}
}
}
} else {
0
}
}
pub fn set_duty_cycle(&mut self, bits: u8) {
self.duty_cycle = match bits {
0 => DutyCycle::Duty0,
1 => DutyCycle::Duty1,
2 => DutyCycle::Duty2,
_ => DutyCycle::Duty3,
};
}
}
impl AudioComponent for SquareWaveGenerator {
fn clock(&mut self) {
self.time += 1f32 / (OUTPUT_SAMPLE_RATE as f32);
}
}

90
src/sound/wave.rs Normal file
View File

@ -0,0 +1,90 @@
// Square wave generator
use crate::sound::AudioComponent;
use crate::sound::OUTPUT_SAMPLE_RATE;
#[derive(Debug, Default, Copy, Clone)]
struct SamplePair(u8);
impl SamplePair {
fn first(self) -> u8 {
self.0 >> 4
}
fn second(self) -> u8 {
self.0 & 0x0F
}
}
impl From<u8> for SamplePair {
fn from(val: u8) -> SamplePair {
SamplePair(val)
}
}
impl Into<u8> for SamplePair {
fn into(self: Self) -> u8 {
self.0
}
}
#[derive(Debug, Default)]
pub struct WaveGenerator {
frequency: f32,
time: f32,
gb_reg_freq: u16,
pub enabled: bool,
samples: [SamplePair; 16],
}
impl WaveGenerator {
fn update_frequency(&mut self) {
self.frequency = ((2048 - self.gb_reg_freq) * 2) as _;
self.time = 0f32;
}
pub fn set_lower_freq(&mut self, freq: u8) {
self.gb_reg_freq &= 0xFF00;
self.gb_reg_freq |= freq as u16;
self.update_frequency();
}
pub fn set_higher_freq(&mut self, freq: u8) {
self.gb_reg_freq &= 0x00FF;
self.gb_reg_freq |= ((freq & 7) as u16) << 8;
self.update_frequency();
}
pub fn reset(&mut self) {
self.time = 0f32;
}
pub fn sample(&self) -> u8 {
if self.enabled && self.frequency > 0f32 {
let temp = self.time * self.frequency;
let position = temp.trunc() as usize % 32;
let sample = &self.samples[position / 2];
match position % 2 {
0 => sample.first() + 120,
_ => sample.second() + 120,
}
} else {
128
}
}
pub fn set_sample_pair(&mut self, nr: usize, val: u8) {
self.samples[nr] = val.into();
}
pub fn get_sample_pair(&self, nr: usize) -> u8 {
self.samples[nr].into()
}
}
impl AudioComponent for WaveGenerator {
fn clock(&mut self) {
self.time += 1f32 / (OUTPUT_SAMPLE_RATE as f32);
}
}

View File

@ -11,43 +11,51 @@ pub struct Timer {
gb_ticks: u16, gb_ticks: u16,
} }
// Timer speed contains 4 different scales:
// [0] = 64 = 4096 Hz
// [1] = 1 = 262144 Hz
// [2] = 4 = 65536 Hz
// [3] = 16 = 16384 Hz
const TIMER_SPEED: [u16; 4] = [64, 1, 4, 16]; const TIMER_SPEED: [u16; 4] = [64, 1, 4, 16];
const TIMER_ENABLE: u8 = (1 << 2); const TIMER_ENABLE: u8 = 1 << 2;
impl Timer { impl Timer {
pub fn new() -> Timer { pub fn new() -> Timer {
Timer::default() Timer::default()
} }
/// This clock is ticking at 262.144 Hz
fn timer_clock_tick(&mut self) { fn timer_clock_tick(&mut self) {
// The div reg will always tick // The div reg will always tick
self.div_tick_counter += 1; self.div_tick_counter += 1;
if self.div_tick_counter >= TIMER_SPEED[3] {
// 262144 Hz / 16 = 16384 Hz
if self.div_tick_counter >= 16 {
self.div = self.div.wrapping_add(1); self.div = self.div.wrapping_add(1);
self.div_tick_counter -= TIMER_SPEED[3]; self.div_tick_counter -= 16;
} }
if (self.tac & TIMER_ENABLE) == TIMER_ENABLE { // Check if the timer is enabled
// Is timer enabled? if (self.tac & TIMER_ENABLE) != 0 {
self.tick_counter += 1; self.tick_counter += 1;
let req_ticks = TIMER_SPEED[(self.tac & 3) as usize]; let req_ticks = TIMER_SPEED[(self.tac & 3) as usize];
if self.tick_counter >= req_ticks { if self.tick_counter == req_ticks {
if self.tima == 0xFF { if self.tima == 0xFF {
self.timer_interrupt = true; self.timer_interrupt = true;
self.tima = 0; self.tima = self.tma;
} else { } else {
self.tima += 1; self.tima += 1;
} }
self.tick_counter -= req_ticks; self.tick_counter = 0;
} }
} }
} }
pub fn tick(&mut self, ticks: u16) { pub fn tick(&mut self, ticks: u16) {
// One tick 1/4.194304MHz on regular speed => 16 gb ticks // One tick 1/4.194304MHz on regular speed => 16 gb ticks = prescaler
self.gb_ticks += ticks; self.gb_ticks += ticks;
// If we're in GBC fast mode, we'd require 32 ticks instead of 16
while self.gb_ticks >= 16 { while self.gb_ticks >= 16 {
self.timer_clock_tick(); self.timer_clock_tick();
self.gb_ticks -= 16; self.gb_ticks -= 16;
@ -55,12 +63,13 @@ impl Timer {
} }
pub fn write_byte(&mut self, addr: u16, val: u8) { pub fn write_byte(&mut self, addr: u16, val: u8) {
// println!("Timer WR: {:04X} = {:02X}", addr, val);
match addr { match addr {
0xFF04 => self.div = 0, 0xFF04 => self.div = 0,
0xFF05 => self.tima = val, 0xFF05 => self.tima = val,
0xFF06 => self.tma = val, 0xFF06 => self.tma = val,
0xFF07 => self.tac = val, 0xFF07 => self.tac = val,
_ => println!("Timer: Write {:02X} to {:04X} unsupported", val, addr), _ => unreachable!(),
} }
} }