Add project files.

This commit is contained in:
Robert 2021-07-12 04:54:24 +02:00
parent d40149497b
commit 54d6fc8c1e
33 changed files with 50685 additions and 0 deletions

26
src/CMakeLists.txt Normal file
View 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
View 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

File diff suppressed because it is too large Load diff

345
src/lcd.cpp Normal file
View 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
View 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
View 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;
}