-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #87 from qoda-dev/beta
Beta
- Loading branch information
Showing
21 changed files
with
7,881 additions
and
15 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,9 @@ | ||
[package] | ||
name = "rustboy" | ||
version = "0.1.0" | ||
name = "qoboy" | ||
version = "1.0.0" | ||
edition = "2018" | ||
|
||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
|
||
[dependencies] | ||
minifb = "0.23.0" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
# Qoboy | ||
|
||
A gameboy emulator with an embedded debugger and a video ram viewer. | ||
|
||
## Installation and running | ||
|
||
Clone and build the projet with the following commands | ||
|
||
```shell | ||
git clone https://github.com/qoda-dev/qoboy.git | ||
|
||
cd qoboy/ | ||
|
||
cargo build --release | ||
``` | ||
|
||
To use the emulator, you have to bring your own **boot rom** and **game rom** files. Then you can run the game with the following command: | ||
|
||
```shell | ||
cargo run <boot_rom_path> <game_rom_path> | ||
``` | ||
|
||
The keyboard mapping is defined as follows: | ||
|
||
| Gameboy control | Keyboard | | ||
| ----------------- | ------- | | ||
| A | a | | ||
| B | z | | ||
| start | backspace | | ||
| select | enter | | ||
| left | left arrow | | ||
| right | right arrow | | ||
| up | up arrow | | ||
| down | down arrow | | ||
|
||
## Embedded debugger | ||
|
||
This emulator comes with an embedded **video ram viewer** and a light **debugger** which can ease the development of your game or your own emulator by using this one as a reference. | ||
|
||
To launch the debugger, add **--debug** when running your game rom: | ||
|
||
```shell | ||
cargo run <boot_rom_path> <game_rom_path> --debug | ||
``` | ||
|
||
Till now, the debugger can handle the following commands: | ||
|
||
| command | argument | description | | ||
| ----------------- | ------- | ------ | | ||
| run | none | run the cpu until it encounters a breakpoint or a halt command is received | | ||
| halt | none | when the cpu is running, halt its execution to the current program counter | | ||
| step | none | when the cpu is halted, execute the instruction pointed by the program counter and update the PC to the next instruction | | ||
| break_set | address | set a breakpoint to the address | | ||
| break_reset | none | reset the breakpoint | | ||
|
||
The emulator can manage only **one breakpoint** and the address passed to the **break_set** command shall meet the following format: | ||
|
||
```shell | ||
break_set C012 | ||
``` | ||
|
||
Where **C012** is the program address on which we want to break in **hexadecimal** format. | ||
|
||
> When launched with the **--debug** option, the emulator stops at address 0x0000 by default and waits for a command just like after a **halt** command has been typed. | ||
> Type **run** or **step** to run your program. | ||
## Tests | ||
|
||
In addition to unit tests for each module, more general functionnal tests are done with blargg's and Acid2 test roms. | ||
|
||
### Blargg's tests | ||
|
||
Source files can be found [here](https://github.com/retrio/gb-test-roms). These roms are used to test general behaviour of CPU, timer and memory subsystems. | ||
|
||
| Blargg's test rom | Comment | Result | | ||
| ----------------- | ------- | ------ | | ||
| cpu_instrs | none | :heavy_check_mark: | | ||
| instr_timing | none | :heavy_check_mark: | | ||
| interrupt_time | need sound to pass | :x: | | ||
| dmg_sound | need sound to pass | :x: | | ||
| oam_bug | not implemented | :x: | | ||
| halt_bug | not implemented | :x: | | ||
| mem_timing | need a clock cycle accurate emulator | :x: | | ||
| mem_timing-2 | need a clock cycle accurate emulator | :x: | | ||
|
||
### Acid2 tests | ||
|
||
Source files can be found [here](https://github.com/mattcurrie/dmg-acid2). This rom is used to test the PPU unit. | ||
|
||
| test rom | Comment | Result | | ||
| -------- | ------- | ------ | | ||
| dmg_acid2 | sprite priority follows GB color behaviour | :x: | | ||
|
||
## Features | ||
|
||
- [X] implement a gameboy emulator which passes all cpu_instr and instr_timing tests | ||
- [X] add support to no_mbc / mbc1 / mbc3 cartridge types | ||
- [X] implement a lightweight debugger | ||
- [X] implement a vram viewer | ||
- [ ] fix sprite priority to pass ACID2 test | ||
- [ ] add possibility to save a game | ||
- [ ] use winit and softbuffer instead of minifb (which is not as stable as expected) | ||
|
||
## Ressources | ||
|
||
### General | ||
|
||
- https://gbdev.io/pandocs/Specifications.html | ||
- https://gbdev.gg8.se/wiki/articles/Main_Page | ||
|
||
### Opcodes | ||
|
||
- https://meganesu.github.io/generate-gb-opcodes/ | ||
- https://www.pastraiser.com/cpu/gameboy/gameboy_opcodes.html | ||
|
||
### Reference emulators | ||
|
||
- https://github.com/Gekkio/mooneye-gb | ||
- https://github.com/mohanson/gameboy | ||
- https://github.com/rylev/DMG-01 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Here can be found custom logo for the project. Use the STARGAZE font for the project title. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
use crate::cartridge::{MbcType, RomSize, RamSize, Mbc}; | ||
|
||
const RAM_ENABLE_SPACE_START: u16 = 0x0000; | ||
const RAM_ENABLE_SPACE_END: u16 = 0x1FFF; | ||
|
||
const ROM_BANK_NB_SPACE_START: u16 = 0x2000; | ||
const ROM_BANK_NB_SPACE_END: u16 = 0x3FFF; | ||
|
||
const RAM_BANK_NB_SPACE_START: u16 = 0x4000; | ||
const RAM_BANK_NB_SPACE_END: u16 = 0x5FFF; | ||
|
||
const BANKING_MODE_SPACE_START: u16 = 0x6000; | ||
const BANKING_MODE_SPACE_END: u16 = 0x7FFF; | ||
|
||
const ENABLE_RAM_FLAG: u8 = 0x0A; | ||
|
||
const GB_ADDR_BIT_MASK: usize = 0x3FFF; | ||
const ROM_BANK_BIT_OFFSET: usize = 14; | ||
const RAM_BANK_BIT_OFFSET: usize = 19; | ||
|
||
#[allow(non_camel_case_types)] | ||
enum RomBankMask { | ||
MASK_1_BIT = 0x01, | ||
MASK_2_BIT = 0x03, | ||
MASK_3_BIT = 0x07, | ||
MASK_4_BIT = 0x0F, | ||
MASK_5_BIT = 0x1F, | ||
} | ||
|
||
pub struct Mbc1 { | ||
// config | ||
rom_size: RomSize, | ||
// internal registers | ||
ram_enable: bool, | ||
rom_bank_number: u8, | ||
ram_bank_number: u8, | ||
banking_mode: bool, | ||
// memory | ||
rom_bank: Vec<u8>, | ||
ram_bank: Vec<u8>, | ||
} | ||
|
||
impl Mbc1 { | ||
pub fn new(_: MbcType, rom_size: RomSize, ram_size: RamSize, rom: &[u8]) -> Mbc1 { | ||
let mut rom_bank: Vec<u8> = vec![0xFF; rom_size.clone() as usize]; | ||
let ram_bank: Vec<u8> = vec![0xFF; ram_size.clone() as usize]; | ||
|
||
// copy all rom data | ||
for rom_index in 0..(rom_size as usize){ | ||
rom_bank[rom_index as usize] = rom[rom_index as usize]; | ||
} | ||
|
||
Mbc1 { | ||
// config | ||
rom_size: rom_size, | ||
// internal registers | ||
ram_enable: false, | ||
rom_bank_number: 1, | ||
ram_bank_number: 0, | ||
banking_mode: false, | ||
// memory | ||
rom_bank: rom_bank, | ||
ram_bank: ram_bank, | ||
} | ||
} | ||
} | ||
|
||
impl Mbc for Mbc1 { | ||
fn read_bank_0 (&self, address: usize) -> u8 { | ||
if self.banking_mode { | ||
let gb_addr = ((self.ram_bank_number as usize) << RAM_BANK_BIT_OFFSET) | (address & GB_ADDR_BIT_MASK); | ||
self.rom_bank[gb_addr] | ||
} else { | ||
let gb_addr = address & GB_ADDR_BIT_MASK; | ||
self.rom_bank[gb_addr] | ||
} | ||
} | ||
|
||
fn read_bank_n (&self, address: usize) -> u8 { | ||
let gb_addr = ((self.ram_bank_number as usize) << RAM_BANK_BIT_OFFSET) | ||
| ((self.rom_bank_number as usize) << ROM_BANK_BIT_OFFSET) | ||
| (address & GB_ADDR_BIT_MASK); | ||
self.rom_bank[gb_addr] | ||
} | ||
|
||
fn read_ram (&self, address: usize) -> u8 { | ||
if self.ram_enable { | ||
if self.banking_mode { | ||
let gb_addr = address & 0x1FFF; | ||
self.ram_bank[gb_addr] | ||
} else { | ||
let gb_addr = ((self.ram_bank_number as usize) << 13) | ||
| (address & 0x1FFF); | ||
self.ram_bank[gb_addr] | ||
} | ||
} else { | ||
// RAM is disabled, returns 0xFF | ||
0xFF | ||
} | ||
} | ||
|
||
fn write_bank_0 (&mut self, address: usize, data: u8) { | ||
match address as u16 { | ||
RAM_ENABLE_SPACE_START..=RAM_ENABLE_SPACE_END => { | ||
if data == ENABLE_RAM_FLAG { | ||
self.ram_enable = true; | ||
} | ||
}, | ||
ROM_BANK_NB_SPACE_START..=ROM_BANK_NB_SPACE_END => { | ||
let rom_bank_mask = match self.rom_size { | ||
RomSize::SIZE_32_KB => RomBankMask::MASK_1_BIT, | ||
RomSize::SIZE_64_KB => RomBankMask::MASK_2_BIT, | ||
RomSize::SIZE_128_KB => RomBankMask::MASK_3_BIT, | ||
RomSize::SIZE_256_KB => RomBankMask::MASK_4_BIT, | ||
_ => RomBankMask::MASK_5_BIT, | ||
}; | ||
|
||
self.rom_bank_number = if data != 0 { | ||
data & (rom_bank_mask as u8) | ||
} else { | ||
// if register is set to 0, set it to 1 | ||
1 | ||
}; | ||
}, | ||
_ => panic!("mbc 1 bank 0 address {:x} doesn't exists.", address), | ||
} | ||
} | ||
|
||
fn write_bank_n (&mut self, address: usize, data: u8) { | ||
match address as u16 { | ||
RAM_BANK_NB_SPACE_START..=RAM_BANK_NB_SPACE_END => { | ||
self.ram_bank_number = data & 0x03; | ||
}, | ||
BANKING_MODE_SPACE_START..=BANKING_MODE_SPACE_END => { | ||
self.banking_mode = (data & 0x01) != 0; | ||
}, | ||
_ => panic!("mbc 1 bank n address {:x} doesn't exists.", address), | ||
} | ||
} | ||
|
||
fn write_ram (&mut self, address: usize, data: u8) { | ||
if self.ram_enable { | ||
if self.banking_mode { | ||
let gb_addr = address & 0x1FFF; | ||
self.ram_bank[gb_addr] = data; | ||
} else { | ||
let gb_addr = ((self.ram_bank_number as usize) << 13) | ||
| (address & 0x1FFF); | ||
self.ram_bank[gb_addr] = data; | ||
} | ||
} else { | ||
// do nothing when ram is disabled | ||
} | ||
} | ||
|
||
// not used for this mbc, doesn't do anything | ||
fn run (&mut self, _: u8) {} | ||
} |
Oops, something went wrong.