Add project files.
This commit is contained in:
parent
d40149497b
commit
54d6fc8c1e
33 changed files with 50685 additions and 0 deletions
26
src/CMakeLists.txt
Normal file
26
src/CMakeLists.txt
Normal file
|
@ -0,0 +1,26 @@
|
|||
file(GLOB_RECURSE IMGUI_SRC
|
||||
"${CMAKE_SOURCE_DIR}/vendor/imgui/src/*.cpp"
|
||||
)
|
||||
|
||||
add_executable(yabgbe)
|
||||
target_sources(yabgbe
|
||||
PRIVATE
|
||||
"main.cpp" "bus.cpp" "cpu.cpp" "rom.cpp" "lcd.cpp"
|
||||
${IMGUI_SRC}
|
||||
PUBLIC
|
||||
${HEADER_FILES}
|
||||
)
|
||||
|
||||
target_include_directories(yabgbe
|
||||
PUBLIC
|
||||
"${CMAKE_SOURCE_DIR}/include"
|
||||
"${CMAKE_SOURCE_DIR}/vendor/imgui/include"
|
||||
SDL2::SDL2 glad::glad
|
||||
)
|
||||
|
||||
target_link_libraries(yabgbe
|
||||
SDL2::SDL2 SDL2::SDL2main
|
||||
glad::glad
|
||||
)
|
||||
|
||||
target_compile_options(yabgbe PRIVATE -Wimplicit-fallthrough)
|
195
src/bus.cpp
Normal file
195
src/bus.cpp
Normal file
|
@ -0,0 +1,195 @@
|
|||
#include "bus.hpp"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
static WORD timerModuloLookup[4] = { 1024, 16, 64, 256 };
|
||||
|
||||
Bus::Bus()
|
||||
{
|
||||
// 8KB of VRAM
|
||||
invalid = 0;
|
||||
dmg_rom = 0;
|
||||
tac.b = 0;
|
||||
joypadReg.b = 0xFF;
|
||||
|
||||
joypad.a = true;
|
||||
joypad.b = true;
|
||||
joypad.down = true;
|
||||
joypad.up = true;
|
||||
joypad.right = true;
|
||||
joypad.left = true;
|
||||
joypad.start = true;
|
||||
joypad.select = true;
|
||||
}
|
||||
|
||||
Bus::~Bus()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void Bus::AttachCPU(CPU& c)
|
||||
{
|
||||
cpu = &c;
|
||||
c.bus = this;
|
||||
}
|
||||
|
||||
void Bus::AttachLCD(LCD& l)
|
||||
{
|
||||
lcd = &l;
|
||||
l.bus = this;
|
||||
|
||||
lcd->Setup();
|
||||
}
|
||||
|
||||
void Bus::InsertROM(ROM& r)
|
||||
{
|
||||
rom = &r;
|
||||
r.bus = this;
|
||||
}
|
||||
|
||||
bool Bus::Tick()
|
||||
{
|
||||
internalCounter++;
|
||||
|
||||
if(!cpu->stopped)
|
||||
cpu->Tick();
|
||||
|
||||
lcd->Tick();
|
||||
|
||||
if (!(internalCounter % 0xFF))
|
||||
div++;
|
||||
|
||||
if (tac.w.enable && !(internalCounter % timerModuloLookup[tac.w.select]))
|
||||
{
|
||||
tima++;
|
||||
if (tima == 0x00)
|
||||
{
|
||||
tima = tma;
|
||||
cpu->interruptFlag.flags.timer = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Bus::Execute()
|
||||
{
|
||||
while (cpu->cycles > 0)
|
||||
{
|
||||
Tick();
|
||||
if (invalid)
|
||||
return false;
|
||||
}
|
||||
|
||||
Tick();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Bus::Frame()
|
||||
{
|
||||
while (lcd->cycles > 0)
|
||||
{
|
||||
Tick();
|
||||
if (invalid)
|
||||
return false;
|
||||
}
|
||||
|
||||
Tick();
|
||||
return true;
|
||||
}
|
||||
|
||||
BYTE Bus::Read(WORD addr)
|
||||
{
|
||||
BYTE returnVal;
|
||||
if (lcd->Read(addr, returnVal))
|
||||
{
|
||||
return returnVal;
|
||||
}
|
||||
|
||||
if ((addr >= 0x0000 && addr < 0x8000) || (addr >= 0xA000 && addr < 0xC000))
|
||||
{
|
||||
return rom->Read(addr);
|
||||
}
|
||||
|
||||
if (addr == 0xFF00)
|
||||
{
|
||||
if (!joypadReg.w.selectButtonKeys) {
|
||||
joypadReg.w.rightA = joypad.a;
|
||||
joypadReg.w.leftB = joypad.b;
|
||||
joypadReg.w.upSelect = joypad.select;
|
||||
joypadReg.w.downStart = joypad.start;
|
||||
}
|
||||
else if (!joypadReg.w.selectDirKeys)
|
||||
{
|
||||
joypadReg.w.rightA = joypad.right;
|
||||
joypadReg.w.leftB = joypad.left;
|
||||
joypadReg.w.upSelect = joypad.up;
|
||||
joypadReg.w.downStart = joypad.down;
|
||||
}
|
||||
|
||||
return joypadReg.b;
|
||||
}
|
||||
|
||||
return GetReference(addr);
|
||||
}
|
||||
|
||||
BYTE Bus::Fetch(WORD addr)
|
||||
{
|
||||
return Read(addr);
|
||||
}
|
||||
|
||||
void Bus::Write(WORD addr, BYTE val)
|
||||
{
|
||||
if (lcd->Write(addr, val))
|
||||
return;
|
||||
|
||||
if ((addr >= 0x0000 && addr < 0x8000) || (addr >= 0xA000 && addr < 0xC000))
|
||||
{
|
||||
rom->Write(addr, val);
|
||||
return;
|
||||
}
|
||||
|
||||
GetReference(addr) = val;
|
||||
undefined = 0xFF;
|
||||
}
|
||||
|
||||
BYTE& Bus::GetReference(WORD addr)
|
||||
{
|
||||
if (addr >= 0xA000 && addr < 0xC000) // Accessing external RAM
|
||||
{
|
||||
return undefined;
|
||||
}
|
||||
else if (addr >= 0xC000 && addr < 0xFE00) // Accessing WRAM / ECHO RAM
|
||||
{
|
||||
return wram[addr & 0x1FFF];
|
||||
}
|
||||
else if (addr >= 0xFEA0 && addr < 0xFF00) // Accessing unusable area???
|
||||
{
|
||||
return undefined;
|
||||
}
|
||||
else if (addr >= 0xFF00 && addr < 0xFF80) // Accessing I/O regs
|
||||
{
|
||||
switch (addr)
|
||||
{
|
||||
case 0xFF00: return joypadReg.b;
|
||||
case 0xFF04: return div;
|
||||
case 0xFF05: return tima;
|
||||
case 0xFF06: return tma;
|
||||
case 0xFF07: return tac.b;
|
||||
case 0xFF0F: return cpu->interruptFlag.b;
|
||||
case 0xFF50: return dmg_rom;
|
||||
}
|
||||
}
|
||||
else if (addr >= 0xFF80 && addr < 0xFFFF) // Accessing HRAM
|
||||
{
|
||||
return hram[addr & 0x7F];
|
||||
}
|
||||
else if (addr == 0xFFFF)
|
||||
{
|
||||
return cpu->interruptEnable.b;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
|
||||
}
|
1170
src/cpu.cpp
Normal file
1170
src/cpu.cpp
Normal file
File diff suppressed because it is too large
Load diff
345
src/lcd.cpp
Normal file
345
src/lcd.cpp
Normal file
|
@ -0,0 +1,345 @@
|
|||
#include "lcd.hpp"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "bus.hpp"
|
||||
|
||||
static BYTE colormap[4] = { 0b00000000, 0b00100101, 0b01001010, 0b10010011 };
|
||||
|
||||
BYTE Reverse(BYTE b) {
|
||||
b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
|
||||
b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
|
||||
b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
|
||||
return b;
|
||||
}
|
||||
|
||||
void LCD::Setup()
|
||||
{
|
||||
lcdc.b = 0;
|
||||
stat.b = 0;
|
||||
scy = 0;
|
||||
scx = 0;
|
||||
ly = 0;
|
||||
lyc = 0;
|
||||
wy = 0;
|
||||
wx = 0;
|
||||
|
||||
cycles = 0;
|
||||
scanlineCycles = 0;
|
||||
|
||||
fetcher.cycle = 0;
|
||||
fetcher.x = 0; fetcher.y = -1;
|
||||
x = 0;
|
||||
dmaCycles = 0;
|
||||
|
||||
bgFIFO.full = 0x00;
|
||||
spriteFIFO.full = 0x00;
|
||||
}
|
||||
|
||||
void LCD::Tick()
|
||||
{
|
||||
// Update cycles
|
||||
scanlineCycles++;
|
||||
cycles++;
|
||||
|
||||
if (scanlineCycles > 455)
|
||||
{
|
||||
fetcher.cycle = 0;
|
||||
scanlineCycles = 0;
|
||||
ly += 1;
|
||||
|
||||
if (ly > 153)
|
||||
{
|
||||
cycles = 0;
|
||||
ly = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Set modes
|
||||
stat.w.coincidence = (lyc == ly);
|
||||
|
||||
// Send interrupts
|
||||
if (ly == 144 && scanlineCycles == 0)
|
||||
{
|
||||
bus->cpu->interruptFlag.flags.vblank = 1;
|
||||
}
|
||||
|
||||
if (
|
||||
(stat.w.lyc && stat.w.coincidence) ||
|
||||
(stat.w.mode2 && stat.w.mode == 2) ||
|
||||
(stat.w.mode1 && stat.w.mode == 1) ||
|
||||
(stat.w.mode0 && stat.w.mode == 0)
|
||||
)
|
||||
{
|
||||
bus->cpu->interruptFlag.flags.lcd_stat = 1;
|
||||
}
|
||||
|
||||
// Screen
|
||||
if (ly >= 0 && ly < 144)
|
||||
{
|
||||
if (scanlineCycles == 0)
|
||||
stat.w.mode = 2;
|
||||
|
||||
else if (scanlineCycles == 81) {
|
||||
stat.w.mode = 3;
|
||||
bgFIFO.full = 0x00;
|
||||
|
||||
x = 0;
|
||||
fetcher.x = 0;
|
||||
fetcher.cycle = 0;
|
||||
|
||||
fetcher.y = (fetcher.y + 1) % 8;
|
||||
}
|
||||
|
||||
|
||||
// OAM Search
|
||||
if (stat.w.mode == 2)
|
||||
{
|
||||
OAMEntry* entry;
|
||||
int counter = 0;
|
||||
for (int i = 0; i < 40; i++)
|
||||
{
|
||||
entry = (OAMEntry*)(oam.data() + i * 4);
|
||||
|
||||
if (entry->b.y == ly)
|
||||
{
|
||||
counter++;
|
||||
if (counter > 10)
|
||||
entry->b.y = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pixel Fetcher
|
||||
else if (stat.w.mode == 3)
|
||||
{
|
||||
if (lcdc.w.obj_enable)
|
||||
{
|
||||
OAMEntry* entry;
|
||||
for (int i = 0; i < 40; i++)
|
||||
{
|
||||
entry = (OAMEntry*)(oam.data() + i * 4);
|
||||
|
||||
if (x == entry->b.x - 8 && entry->b.y <= ly + 16 && ly + 8 + (8 * lcdc.w.obj_size) < entry->b.y)
|
||||
{
|
||||
// Fetch Sprite!
|
||||
WORD yOffset = (ly - entry->b.y + 16);
|
||||
if (entry->b.attr.yFlip)
|
||||
{
|
||||
yOffset = 8 * (1 + lcdc.w.obj_size) - yOffset;
|
||||
}
|
||||
|
||||
BYTE lo = vram[2 * yOffset + (entry->b.idx * 16 * (1 + lcdc.w.obj_size))];
|
||||
BYTE hi = vram[2 * yOffset + (entry->b.idx * 16 * (1 + lcdc.w.obj_size)) + 1];
|
||||
if (entry->b.attr.xFlip)
|
||||
{
|
||||
lo = Reverse(lo);
|
||||
hi = Reverse(hi);
|
||||
}
|
||||
|
||||
spriteFIFO.lowByte = lo;
|
||||
spriteFIFO.highByte = hi;
|
||||
spriteFIFO.full = 0xFF;
|
||||
|
||||
BYTE counter = 0;
|
||||
while (spriteFIFO.full)
|
||||
{
|
||||
BYTE color = ((spriteFIFO.highByte & 0x80) >> 6) | ((spriteFIFO.lowByte & 0x80) >> 7);
|
||||
|
||||
if (color != 0x00)
|
||||
{
|
||||
BYTE bgPriority = (bgFIFO.sprite & (0x80 >> counter)) >> 6;
|
||||
if (!bgPriority)
|
||||
{
|
||||
BYTE bgColor = ((bgFIFO.highByte & (0x80 >> counter)) >> 6) | ((bgFIFO.lowByte & (0x80 >> counter)) >> 7);
|
||||
if (entry->b.attr.bgPriority)
|
||||
{
|
||||
if (bgColor == 0x00)
|
||||
{
|
||||
bgFIFO.highByte ^= ((-((spriteFIFO.highByte & 0x80) >> 7) ^ bgFIFO.highByte) & (0x80 >> counter));
|
||||
bgFIFO.lowByte ^= ((-((spriteFIFO.lowByte & 0x80) >> 7) ^ bgFIFO.lowByte) & (0x80 >> counter));
|
||||
bgFIFO.sprite |= (0x80 >> counter);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bgFIFO.highByte ^= ((((spriteFIFO.highByte & 0x80) >> 7) ^ bgFIFO.highByte) & (0x80 >> counter));
|
||||
bgFIFO.lowByte ^= ((((spriteFIFO.lowByte & 0x80) >> 7) ^ bgFIFO.lowByte) & (0x80 >> counter));
|
||||
bgFIFO.sprite |= (0x80 >> counter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
spriteFIFO.full <<= 1;
|
||||
spriteFIFO.highByte <<= 1;
|
||||
spriteFIFO.lowByte <<= 1;
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (fetcher.cycle)
|
||||
{
|
||||
case 0: // Get Tile
|
||||
{
|
||||
// TODO: Can be different (Window)
|
||||
WORD baseAddr = (lcdc.w.bg_tilemap ? 0x1C00 : 0x1800);
|
||||
BYTE fetcherX = ((scx + fetcher.x) & 0xFF) / 8;
|
||||
BYTE fetcherY = ((ly + scy) & 0xFF) / 8;
|
||||
fetcher.tile = vram[baseAddr + (0x20 * fetcherY) + fetcherX];
|
||||
}
|
||||
break;
|
||||
|
||||
case 2: // Get Tile Data Low
|
||||
{
|
||||
WORD baseAddr = (lcdc.w.tiledata ? 0x0000 : 0x0800);
|
||||
fetcher.lo = vram[baseAddr + 2 * ((fetcher.y + scy) % 8) + (fetcher.tile * 16)];
|
||||
}
|
||||
break;
|
||||
|
||||
case 4: // Get Tile Data High
|
||||
{
|
||||
// TODO: Check LCDC.4
|
||||
WORD baseAddr = (lcdc.w.tiledata ? 0x0000 : 0x0800);
|
||||
fetcher.hi = vram[baseAddr + 2 * ((fetcher.y + scy) % 8) + (fetcher.tile * 16) + 1];
|
||||
}
|
||||
break;
|
||||
|
||||
case 8: // Push
|
||||
if (!(bgFIFO.full & 0x00FF))
|
||||
{
|
||||
bgFIFO.lowByte |= fetcher.lo;
|
||||
bgFIFO.highByte |= fetcher.hi;
|
||||
bgFIFO.sprite |= 0x00;
|
||||
bgFIFO.full |= 0xFF;
|
||||
fetcher.cycle = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
fetcher.cycle--;
|
||||
}
|
||||
|
||||
fetcher.x--;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
fetcher.cycle++;
|
||||
fetcher.x++;
|
||||
|
||||
// Draw pixels
|
||||
if (bgFIFO.full & 0x00FF) // If Data in FIFO
|
||||
{
|
||||
BYTE color = ((bgFIFO.highByte & 0x80) >> 6) | ((bgFIFO.lowByte & 0x80) >> 7);
|
||||
|
||||
display[ly * 160 + x] = colormap[color];
|
||||
x++;
|
||||
|
||||
bgFIFO.highByte <<= 1;
|
||||
bgFIFO.lowByte <<= 1;
|
||||
bgFIFO.full <<= 1;
|
||||
bgFIFO.sprite <<= 1;
|
||||
}
|
||||
|
||||
if (x == 160)
|
||||
{
|
||||
stat.w.mode = 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else if (ly == 144)
|
||||
{
|
||||
stat.w.mode = 1;
|
||||
fetcher.y = -1;
|
||||
}
|
||||
}
|
||||
|
||||
bool LCD::Read(WORD addr, BYTE& val)
|
||||
{
|
||||
if (0x8000 <= addr && addr < 0xA000) // VRAM
|
||||
{
|
||||
if (stat.w.mode != 3 || !lcdc.w.enable)
|
||||
val = vram[addr & 0x1FFF];
|
||||
else
|
||||
val = undefined;
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (0xFE00 <= addr && addr < 0xFEA0) // OAM
|
||||
{
|
||||
if (stat.w.mode == 0 || stat.w.mode == 1 || !lcdc.w.enable)
|
||||
val = oam[addr & 0x9F];
|
||||
else
|
||||
val = undefined;
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (0xFF00 <= addr && addr < 0xFF80) // I/O
|
||||
{
|
||||
switch (addr)
|
||||
{
|
||||
case 0xFF40: val = lcdc.b; return true;
|
||||
case 0xFF41: val = stat.b; return true;
|
||||
case 0xFF42: val = scy; return true;
|
||||
case 0xFF43: val = scx; return true;
|
||||
case 0xFF44: val = ly; return true;
|
||||
case 0xFF45: val = lyc; return true;
|
||||
case 0xFF47: val = bgp; return true;
|
||||
case 0xFF48: val = obp0; return true;
|
||||
case 0xFF49: val = obp1; return true;
|
||||
case 0xFF4A: val = wy; return true;
|
||||
case 0xFF4B: val = wx; return true;
|
||||
|
||||
case 0xFF46: val = dma; return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LCD::Write(WORD addr, BYTE val)
|
||||
{
|
||||
if (0x8000 <= addr && addr < 0xA000) // VRAM
|
||||
{
|
||||
if (stat.w.mode != 3 || !lcdc.w.enable)
|
||||
vram[addr & 0x1FFF] = val;
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (0xFE00 <= addr && addr < 0xFEA0) // OAM
|
||||
{
|
||||
if (stat.w.mode == 0 || stat.w.mode == 1 || !lcdc.w.enable)
|
||||
oam[addr & 0x9F] = val;
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (0xFF00 <= addr && addr < 0xFF80) // I/O
|
||||
{
|
||||
switch (addr)
|
||||
{
|
||||
case 0xFF40: lcdc.b = val; return true;
|
||||
case 0xFF41: stat.b = val; return true;
|
||||
case 0xFF42: scy = val; return true;
|
||||
case 0xFF43: scx = val; return true;
|
||||
case 0xFF44: ly = val; return true;
|
||||
case 0xFF45: lyc = val; return true;
|
||||
case 0xFF47: bgp = val; return true;
|
||||
case 0xFF48: obp0 = val; return true;
|
||||
case 0xFF49: obp1 = val; return true;
|
||||
case 0xFF4A: wy = val; return true;
|
||||
case 0xFF4B: wx = val; return true;
|
||||
|
||||
case 0xFF46: dma = val; dmaCycles = 160;
|
||||
}
|
||||
|
||||
while (dmaCycles != 0)
|
||||
{
|
||||
dmaCycles--;
|
||||
oam[dmaCycles] = bus->Read(((WORD)dma) << 8 | dmaCycles);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
388
src/main.cpp
Normal file
388
src/main.cpp
Normal file
|
@ -0,0 +1,388 @@
|
|||
#include "bus.hpp"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <SDL.h>
|
||||
#include <imgui.h>
|
||||
#include <imgui_sdl.h>
|
||||
|
||||
static BYTE colormap[4] = { 0b00000000, 0b00100101, 0b01001010, 0b10010011 };
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
// Calculate the size of the window? This is literally random lol
|
||||
int width = (512 + 384) * 2 + 10;
|
||||
int height = 256 * 4;
|
||||
|
||||
SDL_Init(SDL_INIT_VIDEO);
|
||||
|
||||
SDL_Window* window = SDL_CreateWindow("Gameboy Emulator", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, SDL_WINDOW_SHOWN);
|
||||
SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
|
||||
SDL_Event e;
|
||||
|
||||
// Initialize ImGui
|
||||
// I use ImGui to display lots of useful info, mainly memory and registers
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
ImGuiSDL::Initialize(renderer, width, height);
|
||||
|
||||
ImGui::StyleColorsDark();
|
||||
|
||||
// To avoid some ImGui weirdness, we need to clear our screen using a texture
|
||||
SDL_Texture* clearScreen = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, 1, 1);
|
||||
{
|
||||
SDL_SetRenderTarget(renderer, clearScreen);
|
||||
SDL_SetRenderDrawColor(renderer, 100, 0, 100, 255);
|
||||
SDL_RenderClear(renderer);
|
||||
SDL_SetRenderTarget(renderer, NULL);
|
||||
}
|
||||
|
||||
// All the textures to old the info we'll be displaying
|
||||
SDL_Texture* ramScreen = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGB332, SDL_TEXTUREACCESS_STREAMING, 128, 64);
|
||||
SDL_Texture* vramScreen = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGB332, SDL_TEXTUREACCESS_STREAMING, 128, 64);
|
||||
SDL_Texture* tilemapRaw1 = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGB332, SDL_TEXTUREACCESS_STREAMING, 32, 32);
|
||||
SDL_Texture* tilemapRaw2 = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGB332, SDL_TEXTUREACCESS_STREAMING, 32, 32);
|
||||
SDL_Texture* tilemap1 = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGB332, SDL_TEXTUREACCESS_STREAMING, 256, 256);
|
||||
SDL_Texture* tilemap2 = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGB332, SDL_TEXTUREACCESS_STREAMING, 256, 256);
|
||||
SDL_Texture* hramScreen = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGB332, SDL_TEXTUREACCESS_STREAMING, 16, 8);
|
||||
SDL_Texture* gameboyScreen = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGB332, SDL_TEXTUREACCESS_STREAMING, 160, 144);
|
||||
|
||||
// aspect ratios for the non-square windows
|
||||
float ramAR = 128.f / 64.f;
|
||||
float vramAR = 128.f / 64.f;
|
||||
float hramAR = 16.f / 8.f;
|
||||
float gbAR = 160.f / 144.f;
|
||||
|
||||
// Initialize the gameboy
|
||||
Bus bus;
|
||||
CPU cpu;
|
||||
LCD lcd;
|
||||
|
||||
bus.AttachCPU(cpu);
|
||||
bus.AttachLCD(lcd);
|
||||
|
||||
// Load the rom
|
||||
if (argc != 2)
|
||||
{
|
||||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Failed to load ROM", "Usage: gbemu <ROM>\nOr drag and drop a ROM onto the executable.", window);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
FILE* f = fopen(argv[1], "rb");
|
||||
ROM rom(f);
|
||||
fclose(f);
|
||||
|
||||
bus.InsertROM(rom);
|
||||
|
||||
cpu.Powerup();
|
||||
|
||||
// Placeholder vars for pixel arrays used to calcualte the rendered tilemaps
|
||||
BYTE* tilemappixels1;
|
||||
int tilemappitch1;
|
||||
BYTE* tilemappixels2;
|
||||
int tilemappitch2;
|
||||
|
||||
// This is literally irrelevant since I'm using a texture to clear anyways? lol
|
||||
SDL_SetRenderDrawColor(renderer, 100, 0, 100, 255);
|
||||
|
||||
// The main program loop
|
||||
bool done = false;
|
||||
while (!done)
|
||||
{
|
||||
// If the emulator hasn't shit itself yet we can run the Gameboy for one frame
|
||||
if (!bus.invalid)
|
||||
bus.Frame();
|
||||
|
||||
// Also give ImGui all the inputs that happened
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
int wheel = 0;
|
||||
|
||||
// Poll for events
|
||||
while (SDL_PollEvent(&e))
|
||||
{
|
||||
// Boring ImGui stuff
|
||||
if (e.type == SDL_QUIT) done = true;
|
||||
else if (e.type == SDL_WINDOWEVENT)
|
||||
{
|
||||
if (e.window.event == SDL_WINDOWEVENT_SIZE_CHANGED)
|
||||
{
|
||||
io.DisplaySize.x = static_cast<float>(e.window.data1);
|
||||
io.DisplaySize.y = static_cast<float>(e.window.data2);
|
||||
}
|
||||
else if(e.window.event == SDL_WINDOWEVENT_CLOSE)
|
||||
{
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
else if (e.type == SDL_MOUSEWHEEL)
|
||||
{
|
||||
wheel = e.wheel.y;
|
||||
}
|
||||
|
||||
// Input for the Joypad. Look at how ugly it is
|
||||
else if (e.type == SDL_KEYUP)
|
||||
{
|
||||
switch (e.key.keysym.sym)
|
||||
{
|
||||
case SDLK_s: bus.joypad.a = true; break;
|
||||
case SDLK_a: bus.joypad.b = true; break;
|
||||
case SDLK_UP: bus.joypad.up = true; break;
|
||||
case SDLK_DOWN: bus.joypad.down = true; break;
|
||||
case SDLK_LEFT: bus.joypad.left = true; break;
|
||||
case SDLK_RIGHT: bus.joypad.right = true; break;
|
||||
case SDLK_LSHIFT: bus.joypad.select = true; break;
|
||||
case SDLK_RETURN: bus.joypad.start = true; break;
|
||||
}
|
||||
}
|
||||
|
||||
else if (e.type == SDL_KEYDOWN)
|
||||
{
|
||||
switch (e.key.keysym.sym)
|
||||
{
|
||||
case SDLK_s: bus.joypad.a = false; cpu.interruptFlag.flags.joypad = 1; break;
|
||||
case SDLK_a: bus.joypad.b = false; cpu.interruptFlag.flags.joypad = 1; break;
|
||||
case SDLK_UP: bus.joypad.up = false; cpu.interruptFlag.flags.joypad = 1; break;
|
||||
case SDLK_DOWN: bus.joypad.down = false; cpu.interruptFlag.flags.joypad = 1; break;
|
||||
case SDLK_LEFT: bus.joypad.left = false; cpu.interruptFlag.flags.joypad = 1; break;
|
||||
case SDLK_RIGHT: bus.joypad.right = false; cpu.interruptFlag.flags.joypad = 1; break;
|
||||
case SDLK_LSHIFT: bus.joypad.select = false; cpu.interruptFlag.flags.joypad = 1; break;
|
||||
case SDLK_RETURN: bus.joypad.start = false; cpu.interruptFlag.flags.joypad = 1; break;
|
||||
}
|
||||
bus.cpu->stopped = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Send all the things that changed to ImGui
|
||||
int mouseX, mouseY;
|
||||
const int buttons = SDL_GetMouseState(&mouseX, &mouseY);
|
||||
|
||||
io.DeltaTime = 1.0f / 60.0f;
|
||||
io.MousePos = ImVec2(static_cast<float>(mouseX), static_cast<float>(mouseY));
|
||||
io.MouseDown[0] = buttons & SDL_BUTTON(SDL_BUTTON_LEFT);
|
||||
io.MouseDown[1] = buttons & SDL_BUTTON(SDL_BUTTON_RIGHT);
|
||||
io.MouseWheel = static_cast<float>(wheel);
|
||||
|
||||
ImGui::NewFrame();
|
||||
|
||||
// Update the memory maps by literally just copying the raw data of the arrays to the texture
|
||||
SDL_UpdateTexture(ramScreen, NULL, bus.wram.data(), 128);
|
||||
SDL_UpdateTexture(vramScreen, NULL, lcd.vram.data(), 128);
|
||||
|
||||
SDL_UpdateTexture(hramScreen, NULL, bus.hram.data(), 16);
|
||||
|
||||
SDL_UpdateTexture(tilemapRaw1, NULL, lcd.vram.data() + 0x1800, 32);
|
||||
SDL_UpdateTexture(tilemapRaw2, NULL, lcd.vram.data() + 0x1C00, 32);
|
||||
|
||||
SDL_UpdateTexture(gameboyScreen, NULL, lcd.display.data(), 160);
|
||||
|
||||
// Just for the rendered tilemap we need to be a bit more elaborate
|
||||
SDL_LockTexture(tilemap1, NULL, (void**)&tilemappixels1, &tilemappitch1);
|
||||
SDL_LockTexture(tilemap2, NULL, (void**)&tilemappixels2, &tilemappitch2);
|
||||
|
||||
// basically this is a quick and dirty PPU background renderer
|
||||
// read about it in the wiki if you wanna know more, i cba to
|
||||
// explain it in a comment here
|
||||
for (int tileY = 0; tileY < 32; tileY++)
|
||||
{
|
||||
for (int tileX = 0; tileX < 32; tileX++)
|
||||
{
|
||||
BYTE tile1ID = lcd.vram[0x1800 + (tileY * 32) + tileX];
|
||||
BYTE tile2ID = lcd.vram[0x1C00 + (tileY * 32) + tileX];
|
||||
|
||||
WORD baseAddr = (lcd.lcdc.w.tiledata ? 0x0000 : 0x0800);
|
||||
WORD addr1 = baseAddr + (tile1ID * 16);
|
||||
WORD addr2 = baseAddr + (tile2ID * 16);
|
||||
|
||||
for (int y = 0; y < 8; y++)
|
||||
{
|
||||
BYTE lo1 = lcd.vram[addr1 + 2 * y];
|
||||
BYTE hi1 = lcd.vram[addr1 + 2 * y + 1];
|
||||
|
||||
BYTE lo2 = lcd.vram[addr2 + 2 * y];
|
||||
BYTE hi2 = lcd.vram[addr2 + 2 * y + 1];
|
||||
for (int x = 0; x < 8; x++)
|
||||
{
|
||||
BYTE loVal1 = (lo1 & (0x80 >> x)) >> (7 - x);
|
||||
BYTE hiVal1 = (hi1 & (0x80 >> x)) >> (7 - x);
|
||||
|
||||
BYTE loVal2 = (lo2 & (0x80 >> x)) >> (7 - x);
|
||||
BYTE hiVal2 = (hi2 & (0x80 >> x)) >> (7 - x);
|
||||
|
||||
tilemappixels1[(tileX * 8 + x) + (32 * 8) * (tileY * 8 + y)] = colormap[loVal1 + hiVal1];
|
||||
tilemappixels2[(tileX * 8 + x) + (32 * 8) * (tileY * 8 + y)] = colormap[loVal2 + hiVal2];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SDL_UnlockTexture(tilemap2);
|
||||
SDL_UnlockTexture(tilemap1);
|
||||
|
||||
|
||||
// Put all the textures in their appropriate ImGui menu
|
||||
ImGui::Begin("WRAM");
|
||||
ImGui::Image(ramScreen, ImVec2(ImGui::GetWindowContentRegionWidth(), ImGui::GetWindowContentRegionWidth() / ramAR));
|
||||
ImGui::End();
|
||||
|
||||
ImGui::Begin("VRAM");
|
||||
ImGui::Text("Raw");
|
||||
ImGui::Image(vramScreen, ImVec2(ImGui::GetWindowContentRegionWidth(), ImGui::GetWindowContentRegionWidth() / vramAR));
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Raw Tilemaps");
|
||||
if (ImGui::BeginTable("tilemaps", 2))
|
||||
{
|
||||
ImGui::TableNextColumn(); ImGui::Image(tilemapRaw1, ImVec2(ImGui::GetWindowContentRegionWidth() / 2, ImGui::GetWindowContentRegionWidth() / 2));
|
||||
ImGui::TableNextColumn(); ImGui::Image(tilemapRaw2, ImVec2(ImGui::GetWindowContentRegionWidth() / 2, ImGui::GetWindowContentRegionWidth() / 2));
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Rendered Tilemaps");
|
||||
if (ImGui::BeginTable("rawtilemaps", 2))
|
||||
{
|
||||
ImGui::TableNextColumn(); ImGui::Image(tilemap1, ImVec2(ImGui::GetWindowContentRegionWidth() / 2, ImGui::GetWindowContentRegionWidth() / 2));
|
||||
ImGui::TableNextColumn(); ImGui::Image(tilemap2, ImVec2(ImGui::GetWindowContentRegionWidth() / 2, ImGui::GetWindowContentRegionWidth() / 2));
|
||||
ImGui::EndTable();
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
ImGui::Begin("HRAM");
|
||||
ImGui::Image(hramScreen, ImVec2(ImGui::GetWindowContentRegionWidth(), ImGui::GetWindowContentRegionWidth() / hramAR));
|
||||
ImGui::End();
|
||||
|
||||
ImGui::Begin("CPU");
|
||||
ImGui::Text("-- Registers --");
|
||||
if (ImGui::BeginTable("Registers", 6))
|
||||
{
|
||||
ImGui::TableNextColumn(); ImGui::Text("AF");
|
||||
ImGui::TableNextColumn(); ImGui::Text("BC");
|
||||
ImGui::TableNextColumn(); ImGui::Text("DE");
|
||||
ImGui::TableNextColumn(); ImGui::Text("HL");
|
||||
ImGui::TableNextColumn(); ImGui::Text("SP");
|
||||
ImGui::TableNextColumn(); ImGui::Text("PC");
|
||||
|
||||
ImGui::TableNextColumn(); ImGui::Text("%04x", cpu.AF.w);
|
||||
ImGui::TableNextColumn(); ImGui::Text("%04x", cpu.BC.w);
|
||||
ImGui::TableNextColumn(); ImGui::Text("%04x", cpu.DE.w);
|
||||
ImGui::TableNextColumn(); ImGui::Text("%04x", cpu.HL.w);
|
||||
ImGui::TableNextColumn(); ImGui::Text("%04x", cpu.SP.w);
|
||||
ImGui::TableNextColumn(); ImGui::Text("%04x", cpu.PC.w);
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
ImGui::Separator();
|
||||
ImGui::Text("-- Status Flags --");
|
||||
if (ImGui::BeginTable("Flags", 4))
|
||||
{
|
||||
ImGui::TableNextColumn(); ImGui::Text("Z");
|
||||
ImGui::TableNextColumn(); ImGui::Text("N");
|
||||
ImGui::TableNextColumn(); ImGui::Text("H");
|
||||
ImGui::TableNextColumn(); ImGui::Text("C");
|
||||
|
||||
ImGui::TableNextColumn(); ImGui::Text("%u", cpu.flag->f.zero);
|
||||
ImGui::TableNextColumn(); ImGui::Text("%u", cpu.flag->f.negative);
|
||||
ImGui::TableNextColumn(); ImGui::Text("%u", cpu.flag->f.halfCarry);
|
||||
ImGui::TableNextColumn(); ImGui::Text("%u", cpu.flag->f.carry);
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
ImGui::Separator();
|
||||
ImGui::Text("-- Interrupts --");
|
||||
ImGui::Text("Interrupts: %s", (cpu.ime ? "ON" : "OFF"));
|
||||
if (ImGui::BeginTable("Interrupts", 6))
|
||||
{
|
||||
ImGui::TableNextColumn(); ImGui::Text("");
|
||||
ImGui::TableNextColumn(); ImGui::Text("V-Blank");
|
||||
ImGui::TableNextColumn(); ImGui::Text("LCD STAT");
|
||||
ImGui::TableNextColumn(); ImGui::Text("Timer");
|
||||
ImGui::TableNextColumn(); ImGui::Text("Serial");
|
||||
ImGui::TableNextColumn(); ImGui::Text("Joypad");
|
||||
|
||||
ImGui::TableNextColumn(); ImGui::Text("IE");
|
||||
ImGui::TableNextColumn(); ImGui::Text("%u", cpu.interruptEnable.flags.vblank);
|
||||
ImGui::TableNextColumn(); ImGui::Text("%u", cpu.interruptEnable.flags.lcd_stat);
|
||||
ImGui::TableNextColumn(); ImGui::Text("%u", cpu.interruptEnable.flags.timer);
|
||||
ImGui::TableNextColumn(); ImGui::Text("%u", cpu.interruptEnable.flags.serial);
|
||||
ImGui::TableNextColumn(); ImGui::Text("%u", cpu.interruptEnable.flags.joypad);
|
||||
|
||||
ImGui::TableNextColumn(); ImGui::Text("IF");
|
||||
ImGui::TableNextColumn(); ImGui::Text("%u", cpu.interruptFlag.flags.vblank);
|
||||
ImGui::TableNextColumn(); ImGui::Text("%u", cpu.interruptFlag.flags.lcd_stat);
|
||||
ImGui::TableNextColumn(); ImGui::Text("%u", cpu.interruptFlag.flags.timer);
|
||||
ImGui::TableNextColumn(); ImGui::Text("%u", cpu.interruptFlag.flags.serial);
|
||||
ImGui::TableNextColumn(); ImGui::Text("%u", cpu.interruptFlag.flags.joypad);
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
if (bus.cpu->stopped)
|
||||
{
|
||||
ImGui::Separator();
|
||||
ImGui::TextColored(ImVec4(255, 0, 0, 255), "STOPPED");
|
||||
}
|
||||
|
||||
if (bus.cpu->halted)
|
||||
{
|
||||
ImGui::Separator();
|
||||
ImGui::TextColored(ImVec4(255, 0, 0, 255), "HALTED");
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
ImGui::Begin("OAM");
|
||||
if (ImGui::BeginTable("OAM", 5))
|
||||
{
|
||||
ImGui::TableNextColumn(); ImGui::Text("$");
|
||||
ImGui::TableNextColumn(); ImGui::Text("Y");
|
||||
ImGui::TableNextColumn(); ImGui::Text("X");
|
||||
ImGui::TableNextColumn(); ImGui::Text("#");
|
||||
ImGui::TableNextColumn(); ImGui::Text("F");
|
||||
|
||||
for (int i = 0; i < 40; i++)
|
||||
{
|
||||
WORD addr = i * 4;
|
||||
ImGui::TableNextColumn(); ImGui::Text("%02x", 0xFE00 + addr);
|
||||
ImGui::TableNextColumn(); ImGui::Text("%03u", lcd.oam[addr + 0x0]);
|
||||
ImGui::TableNextColumn(); ImGui::Text("%03u", lcd.oam[addr + 0x1]);
|
||||
ImGui::TableNextColumn(); ImGui::Text("%02x", lcd.oam[addr + 0x2]);
|
||||
ImGui::TableNextColumn(); ImGui::Text("%02x", lcd.oam[addr + 0x3]);
|
||||
}
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
ImGui::Begin("Gameboy");
|
||||
ImGui::Image(gameboyScreen, ImVec2(ImGui::GetWindowContentRegionWidth(), ImGui::GetWindowContentRegionWidth() / gbAR));
|
||||
ImGui::End();
|
||||
|
||||
// Clear screen and render ImGui
|
||||
SDL_RenderClear(renderer);
|
||||
SDL_RenderCopy(renderer, clearScreen, NULL, NULL);
|
||||
|
||||
ImGui::Render();
|
||||
ImGuiSDL::Render(ImGui::GetDrawData());
|
||||
|
||||
SDL_RenderPresent(renderer);
|
||||
}
|
||||
|
||||
// Free memory & cleanup
|
||||
ImGuiSDL::Deinitialize();
|
||||
ImGui::DestroyContext();
|
||||
|
||||
SDL_DestroyTexture(gameboyScreen);
|
||||
SDL_DestroyTexture(hramScreen);
|
||||
SDL_DestroyTexture(tilemap2);
|
||||
SDL_DestroyTexture(tilemap1);
|
||||
SDL_DestroyTexture(tilemapRaw2);
|
||||
SDL_DestroyTexture(tilemapRaw1);
|
||||
SDL_DestroyTexture(vramScreen);
|
||||
SDL_DestroyTexture(ramScreen);
|
||||
|
||||
SDL_DestroyRenderer(renderer);
|
||||
SDL_DestroyWindow(window);
|
||||
|
||||
SDL_Quit();
|
||||
|
||||
return 0;
|
||||
}
|
104
src/rom.cpp
Normal file
104
src/rom.cpp
Normal file
|
@ -0,0 +1,104 @@
|
|||
#include "rom.hpp"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "bus.hpp"
|
||||
#include "mbcs/mbc0.hpp"
|
||||
#include "mbcs/mbc1.hpp"
|
||||
|
||||
static BYTE bios[0x100] = {
|
||||
0x31, 0xFE, 0xFF, 0xAF, 0x21, 0xFF, 0x9F, 0x32, 0xCB, 0x7C, 0x20, 0xFB, 0x21, 0x26, 0xFF, 0x0E, 0x11, 0x3E, 0x80, 0x32, 0xE2, 0x0C, 0x3E, 0xF3, 0xE2, 0x32, 0x3E, 0x77, 0x77, 0x3E, 0xFC, 0xE0, 0x47, 0x11, 0x04, 0x01, 0x21, 0x10, 0x80, 0x1A, 0xCD, 0x95, 0x00, 0xCD, 0x96, 0x00, 0x13, 0x7B, 0xFE, 0x34, 0x20, 0xF3, 0x11, 0xD8, 0x00, 0x06, 0x08, 0x1A, 0x13, 0x22, 0x23, 0x05, 0x20, 0xF9, 0x3E, 0x19, 0xEA, 0x10, 0x99, 0x21, 0x2F, 0x99, 0x0E, 0x0C, 0x3D, 0x28, 0x08, 0x32, 0x0D, 0x20, 0xF9, 0x2E, 0x0F, 0x18, 0xF3, 0x67, 0x3E, 0x64, 0x57, 0xE0, 0x42, 0x3E, 0x91, 0xE0, 0x40, 0x04, 0x1E, 0x02, 0x0E, 0x0C, 0xF0, 0x44, 0xFE, 0x90, 0x20, 0xFA, 0x0D, 0x20, 0xF7, 0x1D, 0x20, 0xF2, 0x0E, 0x13, 0x24, 0x7C, 0x1E, 0x83, 0xFE, 0x62, 0x28, 0x06, 0x1E, 0xC1, 0xFE, 0x64, 0x20, 0x06, 0x7B, 0xE2, 0x0C, 0x3E, 0x87, 0xE2, 0xF0, 0x42, 0x90, 0xE0, 0x42, 0x15, 0x20, 0xD2, 0x05, 0x20, 0x4F, 0x16, 0x20, 0x18, 0xCB, 0x4F, 0x06, 0x04, 0xC5, 0xCB, 0x11, 0x17, 0xC1, 0xCB, 0x11, 0x17, 0x05, 0x20, 0xF5, 0x22, 0x23, 0x22, 0x23, 0xC9, 0xCE, 0xED, 0x66, 0x66, 0xCC, 0x0D, 0x00, 0x0B, 0x03, 0x73, 0x00, 0x83, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x08, 0x11, 0x1F, 0x88, 0x89, 0x00, 0x0E, 0xDC, 0xCC, 0x6E, 0xE6, 0xDD, 0xDD, 0xD9, 0x99, 0xBB, 0xBB, 0x67, 0x63, 0x6E, 0x0E, 0xEC, 0xCC, 0xDD, 0xDC, 0x99, 0x9F, 0xBB, 0xB9, 0x33, 0x3E, 0x3C, 0x42, 0xB9, 0xA5, 0xB9, 0xA5, 0x42, 0x3C, 0x21, 0x04, 0x01, 0x11, 0xA8, 0x00, 0x1A, 0x13, 0xBE, 0x20, 0xFE, 0x23, 0x7D, 0xFE, 0x34, 0x20, 0xF5, 0x06, 0x19, 0x78, 0x86, 0x23, 0x05, 0x20, 0xFB, 0x86, 0x20, 0xFE, 0x3E, 0x01, 0xE0, 0x50
|
||||
};
|
||||
|
||||
ROM::ROM(FILE* f)
|
||||
{
|
||||
fseek(f, 0, SEEK_END);
|
||||
long fsize = ftell(f);
|
||||
rewind(f);
|
||||
|
||||
data = std::vector<BYTE>(fsize);
|
||||
fread(data.data(), 1, fsize, f);
|
||||
|
||||
switch (data[0x149])
|
||||
{
|
||||
case 0x01: ram = std::vector<BYTE>(0x800); break;
|
||||
case 0x02: ram = std::vector<BYTE>(0x2000); break;
|
||||
case 0x03: ram = std::vector<BYTE>(0x8000); break;
|
||||
case 0x04: ram = std::vector<BYTE>(0x20000); break;
|
||||
case 0x05: ram = std::vector<BYTE>(0x10000); break;
|
||||
}
|
||||
|
||||
WORD RomBanks = (WORD)0x2 << data[0x0148];
|
||||
if (data[0x0148] == 0x52)
|
||||
RomBanks = 72;
|
||||
else if (data[0x0148] == 0x53)
|
||||
RomBanks = 80;
|
||||
else if (data[0x0148] == 0x54)
|
||||
RomBanks = 96;
|
||||
|
||||
WORD RamBanks = 0x00;
|
||||
switch (data[0x0149])
|
||||
{
|
||||
case 0x03: RamBanks = 4; break;
|
||||
case 0x04: RamBanks = 16; break;
|
||||
case 0x05: RamBanks = 8; break;
|
||||
}
|
||||
|
||||
// Select MBC
|
||||
switch (data[0x0147])
|
||||
{
|
||||
case 0x00:
|
||||
mbc = std::make_unique<MBC0>(0);
|
||||
break;
|
||||
|
||||
case 0x01:
|
||||
case 0x02:
|
||||
case 0x03:
|
||||
mbc = std::make_unique<MBC1>(RomBanks, RamBanks, 8);
|
||||
break;
|
||||
|
||||
case 0x08:
|
||||
case 0x09:
|
||||
mbc = std::make_unique<MBC0>(1);
|
||||
break;
|
||||
|
||||
default:
|
||||
EXIT_MSG("This ROM uses an unsupported memory bank controller: %02x\n", data[0x0147]);
|
||||
exit(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
BYTE ROM::Read(WORD addr)
|
||||
{
|
||||
DWORD mappedAddr = 0x00;
|
||||
if (!mbc->GetMappedRead(addr, mappedAddr))
|
||||
return 0xFF;
|
||||
|
||||
switch (bus->Read(0xFF50) + (addr >= 0x100))
|
||||
{
|
||||
case 0:
|
||||
// Read BIOS
|
||||
return bios[addr];
|
||||
|
||||
default:
|
||||
// Read ROM
|
||||
if (addr < 0x8000)
|
||||
return data[mappedAddr];
|
||||
else
|
||||
return ram[mappedAddr];
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
void ROM::Write(WORD addr, BYTE val)
|
||||
{
|
||||
DWORD mappedAddr = 0x00;
|
||||
if (!mbc->GetMappedWrite(addr, val, mappedAddr))
|
||||
return;
|
||||
|
||||
ram[mappedAddr] = val;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue