Added basic support for GBC games

This commit is contained in:
Sour 2020-05-19 21:31:33 -04:00
parent 5f055110fa
commit 371a7a1ac8
19 changed files with 453 additions and 133 deletions

View file

@ -58,12 +58,13 @@ shared_ptr<BaseCartridge> BaseCartridge::CreateCartridge(Console* console, Virtu
cart->_console = console;
cart->_romPath = romFile;
if(FolderUtilities::GetExtension(romFile.GetFileName()) == ".bs") {
string fileExt = FolderUtilities::GetExtension(romFile.GetFileName());
if(fileExt == ".bs") {
cart->_bsxMemPack.reset(new BsxMemoryPack(console, romData, false));
if(!FirmwareHelper::LoadBsxFirmware(console, &cart->_prgRom, cart->_prgRomSize)) {
return nullptr;
}
} else if(FolderUtilities::GetExtension(romFile.GetFileName()) == ".gb") {
} else if(fileExt == ".gb" || fileExt == ".gbc") {
if(cart->LoadGameboy(romFile)) {
return cart;
} else {

View file

@ -49,11 +49,15 @@ Gameboy* Gameboy::Create(Console* console, VirtualFile &romFile)
gb->_cartRam = new uint8_t[gb->_cartRamSize];
gb->_hasBattery = header.HasBattery();
gb->_workRam = new uint8_t[Gameboy::WorkRamSize];
gb->_videoRam = new uint8_t[Gameboy::VideoRamSize];
gb->_cgbMode = (header.CgbFlag & 0x80) != 0;
gb->_workRamSize = gb->_cgbMode ? 0x8000 : 0x2000;
gb->_videoRamSize = gb->_cgbMode ? 0x4000 : 0x2000;
gb->_workRam = new uint8_t[gb->_workRamSize];
gb->_videoRam = new uint8_t[gb->_videoRamSize];
gb->_spriteRam = new uint8_t[Gameboy::SpriteRamSize];
gb->_highRam = new uint8_t[Gameboy::HighRamSize];
return gb;
}
@ -78,12 +82,12 @@ void Gameboy::PowerOn()
{
EmuSettings* settings = _console->GetSettings().get();
settings->InitializeRam(_cartRam, _cartRamSize);
settings->InitializeRam(_workRam, Gameboy::WorkRamSize);
settings->InitializeRam(_workRam, _workRamSize);
settings->InitializeRam(_spriteRam, Gameboy::SpriteRamSize);
settings->InitializeRam(_highRam, Gameboy::HighRamSize);
//VRAM is filled with 0s by the boot rom
memset(_videoRam, 0, Gameboy::VideoRamSize);
memset(_videoRam, 0, _videoRamSize);
LoadBattery();
@ -94,7 +98,7 @@ void Gameboy::PowerOn()
_cart->Init(this, _memoryManager.get());
_memoryManager->Init(_console, this, _cart.get(), _ppu.get(), _apu.get(), _timer.get());
_cpu.reset(new GbCpu(_memoryManager.get()));
_cpu.reset(new GbCpu(this, _memoryManager.get()));
_ppu->Init(_console, this, _memoryManager.get(), _videoRam, _spriteRam);
}
@ -127,6 +131,7 @@ void Gameboy::SaveBattery()
GbState Gameboy::GetState()
{
GbState state;
state.Type = IsCgb() ? GbType::Cgb : GbType::Gb;
state.Cpu = _cpu->GetState();
state.Ppu = _ppu->GetState();
state.Apu = _apu->GetState();
@ -139,8 +144,8 @@ uint32_t Gameboy::DebugGetMemorySize(SnesMemoryType type)
{
switch(type) {
case SnesMemoryType::GbPrgRom: return _prgRomSize;
case SnesMemoryType::GbWorkRam: return Gameboy::WorkRamSize;
case SnesMemoryType::GbVideoRam: return Gameboy::VideoRamSize;
case SnesMemoryType::GbWorkRam: return _workRamSize;
case SnesMemoryType::GbVideoRam: return _videoRamSize;
case SnesMemoryType::GbCartRam: return _cartRamSize;
case SnesMemoryType::GbHighRam: return Gameboy::HighRamSize;
default: return 0;
@ -190,7 +195,7 @@ AddressInfo Gameboy::GetAbsoluteAddress(uint16_t addr)
if(ptr >= _prgRom && ptr < _prgRom + _prgRomSize) {
addrInfo.Address = (int32_t)(ptr - _prgRom);
addrInfo.Type = SnesMemoryType::GbPrgRom;
} else if(ptr >= _workRam && ptr < _workRam + Gameboy::WorkRamSize) {
} else if(ptr >= _workRam && ptr < _workRam + _workRamSize) {
addrInfo.Address = (int32_t)(ptr - _workRam);
addrInfo.Type = SnesMemoryType::GbWorkRam;
} else if(ptr >= _cartRam && ptr < _cartRam + _cartRamSize) {
@ -216,11 +221,21 @@ int32_t Gameboy::GetRelativeAddress(AddressInfo& absAddress)
return -1;
}
bool Gameboy::IsCgb()
{
return _cgbMode;
}
uint64_t Gameboy::GetCycleCount()
{
return _cpu->GetCycleCount();
}
uint64_t Gameboy::GetApuCycleCount()
{
return _memoryManager->GetApuCycleCount();
}
void Gameboy::Serialize(Serializer& s)
{
s.Stream(_cpu.get());
@ -232,8 +247,8 @@ void Gameboy::Serialize(Serializer& s)
s.Stream(_hasBattery);
s.StreamArray(_cartRam, _cartRamSize);
s.StreamArray(_workRam, Gameboy::WorkRamSize);
s.StreamArray(_videoRam, Gameboy::VideoRamSize);
s.StreamArray(_workRam, _workRamSize);
s.StreamArray(_videoRam, _videoRamSize);
s.StreamArray(_spriteRam, Gameboy::SpriteRamSize);
s.StreamArray(_highRam, Gameboy::HighRamSize);
}

View file

@ -15,8 +15,6 @@ class VirtualFile;
class Gameboy : public ISerializable
{
private:
static constexpr int WorkRamSize = 0x2000;
static constexpr int VideoRamSize = 0x2000;
static constexpr int SpriteRamSize = 0xA0;
static constexpr int HighRamSize = 0x7F;
@ -30,6 +28,7 @@ private:
unique_ptr<GbTimer> _timer;
bool _hasBattery;
bool _cgbMode;
uint8_t* _prgRom;
uint32_t _prgRomSize;
@ -38,7 +37,11 @@ private:
uint32_t _cartRamSize;
uint8_t* _workRam;
uint32_t _workRamSize;
uint8_t* _videoRam;
uint32_t _videoRamSize;
uint8_t* _spriteRam;
uint8_t* _highRam;
@ -62,7 +65,9 @@ public:
AddressInfo GetAbsoluteAddress(uint16_t addr);
int32_t GetRelativeAddress(AddressInfo& absAddress);
bool IsCgb();
uint64_t GetCycleCount();
uint64_t GetApuCycleCount();
void Serialize(Serializer& s) override;
};

View file

@ -42,58 +42,58 @@ GbApuDebugState GbApu::GetState()
void GbApu::Run()
{
uint64_t clockCount = _gameboy->GetCycleCount();
uint64_t clockCount = _gameboy->GetApuCycleCount();
uint32_t clocksToRun = (uint32_t)(clockCount - _prevClockCount);
_prevClockCount = clockCount;
if(!_state.ApuEnabled) {
return;
_clockCounter += clocksToRun;
} else {
while(clocksToRun > 0) {
uint32_t minTimer = std::min<uint32_t>({ clocksToRun, _square1.GetState().Timer, _square2.GetState().Timer, _wave.GetState().Timer, _noise.GetState().Timer });
clocksToRun -= minTimer;
_square1.Exec(minTimer);
_square2.Exec(minTimer);
_wave.Exec(minTimer);
_noise.Exec(minTimer);
int16_t leftOutput = (
(_square1.GetOutput() & _state.EnableLeftSq1) +
(_square2.GetOutput() & _state.EnableLeftSq2) +
(_wave.GetOutput() & _state.EnableLeftWave) +
(_noise.GetOutput() & _state.EnableLeftNoise)
) * (_state.LeftVolume + 1) * 40;
if(_prevLeftOutput != leftOutput) {
blip_add_delta(_leftChannel, _clockCounter, leftOutput - _prevLeftOutput);
_prevLeftOutput = leftOutput;
}
int16_t rightOutput = (
(_square1.GetOutput() & _state.EnableRightSq1) +
(_square2.GetOutput() & _state.EnableRightSq2) +
(_wave.GetOutput() & _state.EnableRightWave) +
(_noise.GetOutput() & _state.EnableRightNoise)
) * (_state.RightVolume + 1) * 40;
if(_prevRightOutput != rightOutput) {
blip_add_delta(_rightChannel, _clockCounter, rightOutput - _prevRightOutput);
_prevRightOutput = rightOutput;
}
_clockCounter += minTimer;
}
}
while(clocksToRun > 0) {
uint32_t minTimer = std::min<uint32_t>({ clocksToRun, _square1.GetState().Timer, _square2.GetState().Timer, _wave.GetState().Timer, _noise.GetState().Timer });
if(_clockCounter >= 20000) {
blip_end_frame(_leftChannel, _clockCounter);
blip_end_frame(_rightChannel, _clockCounter);
clocksToRun -= minTimer;
_square1.Exec(minTimer);
_square2.Exec(minTimer);
_wave.Exec(minTimer);
_noise.Exec(minTimer);
int16_t leftOutput = (
(_square1.GetOutput() & _state.EnableLeftSq1) +
(_square2.GetOutput() & _state.EnableLeftSq2) +
(_wave.GetOutput() & _state.EnableLeftWave) +
(_noise.GetOutput() & _state.EnableLeftNoise)
) * (_state.LeftVolume + 1) * 40;
if(_prevLeftOutput != leftOutput) {
blip_add_delta(_leftChannel, _clockCounter, leftOutput - _prevLeftOutput);
_prevLeftOutput = leftOutput;
}
int16_t rightOutput = (
(_square1.GetOutput() & _state.EnableRightSq1) +
(_square2.GetOutput() & _state.EnableRightSq2) +
(_wave.GetOutput() & _state.EnableRightWave) +
(_noise.GetOutput() & _state.EnableRightNoise)
) * (_state.RightVolume + 1) * 40;
if(_prevRightOutput != rightOutput) {
blip_add_delta(_rightChannel, _clockCounter, rightOutput - _prevRightOutput);
_prevRightOutput = rightOutput;
}
_clockCounter += minTimer;
if(_clockCounter >= 20000) {
blip_end_frame(_leftChannel, _clockCounter);
blip_end_frame(_rightChannel, _clockCounter);
uint32_t sampleCount = (uint32_t)blip_read_samples(_leftChannel, _soundBuffer, GbApu::MaxSamples, 1);
blip_read_samples(_rightChannel, _soundBuffer + 1, GbApu::MaxSamples, 1);
_console->GetSoundMixer()->PlayAudioBuffer(_soundBuffer, sampleCount, GbApu::SampleRate);
_clockCounter = 0;
}
uint32_t sampleCount = (uint32_t)blip_read_samples(_leftChannel, _soundBuffer, GbApu::MaxSamples, 1);
blip_read_samples(_rightChannel, _soundBuffer + 1, GbApu::MaxSamples, 1);
_console->GetSoundMixer()->PlayAudioBuffer(_soundBuffer, sampleCount, GbApu::SampleRate);
_clockCounter = 0;
}
}

View file

@ -2,6 +2,8 @@
#include "stdafx.h"
#include "Gameboy.h"
#include "GbMemoryManager.h"
#include "MessageManager.h"
#include "../Utilities/HexUtilities.h"
#include "../Utilities/ISerializable.h"
class GbCart : public ISerializable
@ -44,6 +46,7 @@ public:
virtual uint8_t ReadRegister(uint16_t addr)
{
LogDebug("[Debug] GB - Missing read handler: $" + HexUtilities::ToHex(addr));
return 0;
}

View file

@ -1,10 +1,12 @@
#include "stdafx.h"
#include "GbCpu.h"
#include "Gameboy.h"
#include "GbMemoryManager.h"
#include "../Utilities/Serializer.h"
GbCpu::GbCpu(GbMemoryManager* memoryManager)
GbCpu::GbCpu(Gameboy* gameboy, GbMemoryManager* memoryManager)
{
_gameboy = gameboy;
_memoryManager = memoryManager;
_state = {};
@ -14,7 +16,7 @@ GbCpu::GbCpu(GbMemoryManager* memoryManager)
#else
_state.PC = 0x100;
_state.SP = 0xFFFE;
_state.A = 0x01;
_state.A = gameboy->IsCgb() ? 0x11 : 0x01;
_state.B = 0x00;
_state.C = 0x13;
_state.D = 0x00;
@ -691,7 +693,11 @@ void GbCpu::InvalidOp()
void GbCpu::STOP()
{
if(_gameboy->IsCgb() && _memoryManager->GetState().CgbSwitchSpeedRequest) {
_memoryManager->ToggleSpeed();
} else {
_state.Halted = true;
}
}
void GbCpu::HALT()

View file

@ -4,6 +4,7 @@
#include "../Utilities/ISerializable.h"
class GbMemoryManager;
class Gameboy;
class GbCpu : public ISerializable
{
@ -15,9 +16,10 @@ private:
Register16 _regHL = Register16(&_state.H, &_state.L);
GbMemoryManager* _memoryManager;
Gameboy* _gameboy;
public:
GbCpu(GbMemoryManager* memoryManager);
GbCpu(Gameboy* gameboy, GbMemoryManager* memoryManager);
virtual ~GbCpu();
GbCpuState GetState();

View file

@ -9,14 +9,17 @@
#include "GbCart.h"
#include "EmuSettings.h"
#include "ControlManager.h"
#include "SnesController.h"
#include "MessageManager.h"
#include "../Utilities/VirtualFile.h"
#include "../Utilities/Serializer.h"
#include "SnesController.h"
#include "../Utilities/HexUtilities.h"
void GbMemoryManager::Init(Console* console, Gameboy* gameboy, GbCart* cart, GbPpu* ppu, GbApu* apu, GbTimer* timer)
{
_state = {};
_state.DisableBootRom = true;
_state.CgbWorkRamBank = 1;
_prgRom = gameboy->DebugGetMemory(SnesMemoryType::GbPrgRom);
_prgRomSize = gameboy->DebugGetMemorySize(SnesMemoryType::GbPrgRom);
@ -58,14 +61,17 @@ void GbMemoryManager::RefreshMappings()
//Map(0x0000, 0x00FF, GbMemoryType::WorkRam, true);
}
Map(0xC000, 0xDFFF, GbMemoryType::WorkRam, 0, false);
Map(0xE000, 0xFCFF, GbMemoryType::WorkRam, 0, false);
Map(0xC000, 0xCFFF, GbMemoryType::WorkRam, 0, false);
Map(0xD000, 0xDFFF, GbMemoryType::WorkRam, _state.CgbWorkRamBank * 0x1000, false);
Map(0xE000, 0xEFFF, GbMemoryType::WorkRam, 0, false);
Map(0xF000, 0xFCFF, GbMemoryType::WorkRam, _state.CgbWorkRamBank * 0x1000, false);
_cart->RefreshMappings();
}
void GbMemoryManager::Exec()
{
_state.ApuCycleCount += _state.CgbHighSpeed ? 2 : 4;
_timer->Exec();
_ppu->Exec();
}
@ -180,10 +186,25 @@ uint8_t GbMemoryManager::ReadRegister(uint16_t addr)
return _state.IrqEnabled; //IE - Interrupt Enable (R/W)
} else if(addr >= 0xFF80) {
return _highRam[addr & 0x7F]; //80-FE
} else if(addr >= 0xFF50) {
return 0; //50-7F
} else if(addr >= 0xFF4D) {
if(_gameboy->IsCgb()) {
switch(addr) {
//FF4D - KEY1 - CGB Mode Only - Prepare Speed Switch
case 0xFF4D: return _state.CgbHighSpeed ? 0x80 : 0;
case 0xFF4F: //CGB - VRAM bank
case 0xFF51: case 0xFF52: case 0xFF53: case 0xFF54: case 0xFF55: //CGB - DMA
case 0xFF68: case 0xFF69: case 0xFF6A: case 0xFF6B: //CGB - Palette
return _ppu->ReadCgbRegister(addr);
//FF70 - SVBK - CGB Mode Only - WRAM Bank
case 0xFF70: return _state.CgbWorkRamBank;
}
}
LogDebug("[Debug] GB - Missing read handler: $" + HexUtilities::ToHex(addr));
return 0; //4D-7F
} else if(addr >= 0xFF40) {
return _ppu->Read(addr); //40-4F
return _ppu->Read(addr); //40-4C
} else if(addr >= 0xFF10) {
return _apu->Read(addr); //10-3F
} else {
@ -214,14 +235,39 @@ void GbMemoryManager::WriteRegister(uint16_t addr, uint8_t value)
_state.IrqEnabled = value; //IE register
} else if(addr >= 0xFF80) {
_highRam[addr & 0x7F] = value; //80-FE
} else if(addr >= 0xFF50) {
//50-7F
if(addr == 0xFF50 && (value & 0x01)) {
_state.DisableBootRom = true;
RefreshMappings();
} else if(addr >= 0xFF4D) {
//4D-7F
if(addr == 0xFF50) {
if(value & 0x01) {
_state.DisableBootRom = true;
RefreshMappings();
}
} else if(_gameboy->IsCgb()) {
switch(addr) {
case 0xFF4D:
//FF4D - KEY1 - CGB Mode Only - Prepare Speed Switch
_state.CgbSwitchSpeedRequest = (value & 0x01) != 0;
break;
case 0xFF4F: //CGB - VRAM banking
case 0xFF51: case 0xFF52: case 0xFF53: case 0xFF54: case 0xFF55: //CGB - DMA
case 0xFF68: case 0xFF69: case 0xFF6A: case 0xFF6B: //CGB - Palette
_ppu->WriteCgbRegister(addr, value);
break;
case 0xFF70:
//FF70 - SVBK - CGB Mode Only - WRAM Bank
_state.CgbWorkRamBank = std::max(1, value & 0x07);
RefreshMappings();
break;
default:
LogDebug("[Debug] GBC - Missing write handler: $" + HexUtilities::ToHex(addr));
break;
}
}
} else if(addr >= 0xFF40) {
_ppu->Write(addr, value); //40-4F
_ppu->Write(addr, value); //40-4C
} else if(addr >= 0xFF10) {
_apu->Write(addr, value); //10-3F
} else {
@ -275,6 +321,22 @@ uint8_t GbMemoryManager::ProcessIrqRequests()
return 0;
}
void GbMemoryManager::ToggleSpeed()
{
_state.CgbSwitchSpeedRequest = false;
_state.CgbHighSpeed = !_state.CgbHighSpeed;
}
bool GbMemoryManager::IsHighSpeed()
{
return _state.CgbHighSpeed;
}
uint64_t GbMemoryManager::GetApuCycleCount()
{
return _state.ApuCycleCount;
}
uint8_t GbMemoryManager::ReadInputPort()
{
//Bit 7 - Not used
@ -307,7 +369,10 @@ uint8_t GbMemoryManager::ReadInputPort()
void GbMemoryManager::Serialize(Serializer& s)
{
s.Stream(_state.DisableBootRom, _state.IrqEnabled, _state.IrqRequests, _state.InputSelect);
s.Stream(
_state.DisableBootRom, _state.IrqEnabled, _state.IrqRequests, _state.InputSelect,
_state.ApuCycleCount, _state.CgbHighSpeed, _state.CgbSwitchSpeedRequest, _state.CgbWorkRamBank
);
s.StreamArray(_state.MemoryType, 0x100);
s.StreamArray(_state.MemoryOffset, 0x100);
s.StreamArray(_state.MemoryAccessType, 0x100);

View file

@ -61,6 +61,10 @@ public:
void RequestIrq(uint8_t source);
void ClearIrqRequest(uint8_t source);
uint8_t ProcessIrqRequests();
void ToggleSpeed();
bool IsHighSpeed();
uint64_t GetApuCycleCount();
uint8_t ReadInputPort();

View file

@ -8,6 +8,8 @@
#include "RewindManager.h"
#include "GbMemoryManager.h"
#include "NotificationManager.h"
#include "MessageManager.h"
#include "../Utilities/HexUtilities.h"
#include "../Utilities/Serializer.h"
void GbPpu::Init(Console* console, Gameboy* gameboy, GbMemoryManager* memoryManager, uint8_t* vram, uint8_t* oam)
@ -56,9 +58,9 @@ void GbPpu::Exec()
if(!_state.LcdEnabled) {
//LCD is disabled, prevent IRQs, etc.
//Not quite correct in terms of frame pacing
if(_gameboy->GetCycleCount() - _lastFrameTime > 70224) {
if(_gameboy->GetApuCycleCount() - _lastFrameTime > 70224) {
//More than a full frame's worth of time has passed since the last frame, send another blank frame
_lastFrameTime = _gameboy->GetCycleCount();
_lastFrameTime = _gameboy->GetApuCycleCount();
SendFrame();
}
return;
@ -66,8 +68,10 @@ void GbPpu::Exec()
ExecCycle();
ExecCycle();
ExecCycle();
ExecCycle();
if(!_memoryManager->IsHighSpeed()) {
ExecCycle();
ExecCycle();
}
}
void GbPpu::ExecCycle()
@ -121,7 +125,12 @@ void GbPpu::ExecCycle()
if(_state.Status & GbPpuStatusFlags::HBlankIrq) {
_memoryManager->RequestIrq(GbIrqSource::LcdStat);
}
RenderScanline();
if(_gameboy->IsCgb()) {
RenderScanline<true>();
} else {
RenderScanline<false>();
}
}
}
}
@ -136,13 +145,16 @@ void GbPpu::GetPalette(uint16_t out[4], uint8_t palCfg)
out[3] = rgbPalette[(palCfg >> 6) & 0x03];
}
template<bool isCgb>
void GbPpu::RenderScanline()
{
uint16_t bgColors[4];
uint16_t oamColors[2][4];
GetPalette(bgColors, _state.BgPalette);
GetPalette(oamColors[0], _state.ObjPalette0);
GetPalette(oamColors[1], _state.ObjPalette1);
if(!isCgb) {
GetPalette(bgColors, _state.BgPalette);
GetPalette(oamColors[0], _state.ObjPalette0);
GetPalette(oamColors[1], _state.ObjPalette1);
}
uint8_t visibleSprites[10] = {};
uint8_t spriteCount = 0;
@ -157,6 +169,7 @@ void GbPpu::RenderScanline()
}
}
//TODO option toggle for CGB
if(spriteCount > 1) {
//Sort sprites by their X position first, and then by their index when X values are equal
std::sort(visibleSprites, visibleSprites + spriteCount, [=](uint8_t a, uint8_t b) {
@ -175,9 +188,10 @@ void GbPpu::RenderScanline()
for(int x = 0; x < 160; x++) {
uint8_t bgColor = 0;
uint8_t bgPalette = 0;
bool bgPriority = false;
uint16_t outOffset = _state.Scanline * 256 + x;
if(_state.BgEnabled) {
if(_state.WindowEnabled && x >= _state.WindowX - 7 && _state.Scanline >= _state.WindowY) {
//Draw window content instead
tilemapAddr = _state.WindowTilemapSelect ? 0x1C00 : 0x1800;
@ -191,27 +205,41 @@ void GbPpu::RenderScanline()
}
uint8_t row = yOffset >> 3;
uint8_t tileY = yOffset & 0x07;
uint8_t column = xOffset >> 3;
uint8_t tileIndex = _vram[tilemapAddr + column + row * 32];
uint16_t tileAddr = tilemapAddr + column + row * 32;
uint8_t tileIndex = _vram[tileAddr];
uint8_t attributes = isCgb ? _vram[tileAddr | 0x2000] : 0;
bgPalette = (attributes & 0x07) << 2;
uint16_t tileBank = (attributes & 0x08) ? 0x2000 : 0x0000;
bool hMirror = (attributes & 0x20) != 0;
bool vMirror = (attributes & 0x40) != 0;
bgPriority = (attributes & 0x80) != 0;
uint8_t tileY = vMirror ? (7 - (yOffset & 0x07)) : (yOffset & 0x07);
uint16_t tileRowAddr = baseTile + (baseTile ? (int8_t)tileIndex * 16 : tileIndex * 16) + tileY * 2;
uint8_t shift = 7 - (xOffset & 0x07);
tileRowAddr |= tileBank;
uint8_t shift = hMirror ? (xOffset & 0x07) : (7 - (xOffset & 0x07));
bgColor = ((_vram[tileRowAddr] >> shift) & 0x01) | (((_vram[tileRowAddr + 1] >> shift) & 0x01) << 1);
}
_currentBuffer[outOffset] = bgColors[bgColor];
_currentBuffer[outOffset] = isCgb ? _state.CgbBgPalettes[bgColor | bgPalette] : bgColors[bgColor];
if(_state.SpritesEnabled && spriteCount) {
if(!bgPriority && _state.SpritesEnabled && spriteCount) {
for(int i = 0; i < spriteCount; i++) {
uint8_t sprIndex = visibleSprites[i];
int16_t sprX = (int16_t)_oam[sprIndex + 1] - 8;
if(x >= sprX && x < sprX + 8) {
int16_t sprY = (int16_t)_oam[sprIndex] - 16;
uint8_t sprTile = _oam[sprIndex + 2];
bool bgPriority = (_oam[sprIndex + 3] & 0x80);
bool vMirror = (_oam[sprIndex + 3] & 0x40);
bool hMirror = (_oam[sprIndex + 3] & 0x20);
bool palette = (_oam[sprIndex + 3] & 0x10);
uint8_t sprAttr = _oam[sprIndex + 3];
bool bgPriority = (sprAttr & 0x80) != 0;
bool vMirror = (sprAttr & 0x40) != 0;
bool hMirror = (sprAttr & 0x20) != 0;
uint8_t sprPalette = (sprAttr & 0x07) << 2;
uint16_t tileBank = (sprAttr & 0x08) ? 0x2000 : 0x0000;
uint8_t sprOffsetY = vMirror ? (_state.LargeSprites ? 15 : 7) - (_state.Scanline - sprY) : (_state.Scanline - sprY);
if(_state.LargeSprites) {
@ -219,10 +247,10 @@ void GbPpu::RenderScanline()
}
uint8_t sprShiftX = hMirror ? (x - sprX) : 7 - (x - sprX);
uint16_t sprTileAddr = sprTile * 16 + sprOffsetY * 2;
uint16_t sprTileAddr = (sprTile * 16 + sprOffsetY * 2) | tileBank;
uint8_t sprColor = ((_vram[sprTileAddr] >> sprShiftX) & 0x01) | (((_vram[sprTileAddr + 1] >> sprShiftX) & 0x01) << 1);
if(sprColor > 0 && (bgColor == 0 || !bgPriority)) {
_currentBuffer[outOffset] = oamColors[(int)palette][sprColor];
_currentBuffer[outOffset] = isCgb ? _state.CgbObjPalettes[sprColor | sprPalette] : oamColors[(int)sprPalette][sprColor];
break;
}
}
@ -280,6 +308,8 @@ uint8_t GbPpu::Read(uint16_t addr)
case 0xFF4A: return _state.WindowY; //FF4A - WY - Window Y Position (R/W)
case 0xFF4B: return _state.WindowX; //FF4B - WX - Window X Position minus 7 (R/W)
}
LogDebug("[Debug] GB - Missing read handler: $" + HexUtilities::ToHex(addr));
return 0;
}
@ -299,7 +329,8 @@ void GbPpu::Write(uint16_t addr, uint8_t value)
//Send a blank (white) frame
_lastFrameTime = _gameboy->GetCycleCount();
std::fill(_currentBuffer, _currentBuffer + 256 * 239, 0x7FFF);
std::fill(_outputBuffers[0], _outputBuffers[0] + 256 * 239, 0x7FFF);
std::fill(_outputBuffers[1], _outputBuffers[1] + 256 * 239, 0x7FFF);
SendFrame();
}
}
@ -329,13 +360,17 @@ void GbPpu::Write(uint16_t addr, uint8_t value)
case 0xFF49: _state.ObjPalette1 = value; break;
case 0xFF4A: _state.WindowY = value; break;
case 0xFF4B: _state.WindowX = value; break;
default:
LogDebug("[Debug] GB - Missing write handler: $" + HexUtilities::ToHex(addr));
break;
}
}
uint8_t GbPpu::ReadVram(uint16_t addr)
{
if((int)_state.Mode <= (int)PpuMode::OamEvaluation) {
return _vram[addr & 0x1FFF];
return _vram[(_state.CgbVramBank << 13) | (addr & 0x1FFF)];
} else {
return 0xFF;
}
@ -344,7 +379,7 @@ uint8_t GbPpu::ReadVram(uint16_t addr)
void GbPpu::WriteVram(uint16_t addr, uint8_t value)
{
if((int)_state.Mode <= (int)PpuMode::OamEvaluation) {
_vram[addr & 0x1FFF] = value;
_vram[(_state.CgbVramBank << 13) | (addr & 0x1FFF)] = value;
}
}
@ -367,12 +402,102 @@ void GbPpu::WriteOam(uint8_t addr, uint8_t value)
}
}
uint8_t GbPpu::ReadCgbRegister(uint16_t addr)
{
switch(addr) {
case 0xFF4F: return _state.CgbVramBank;
case 0xFF51: return _state.CgbDmaSource >> 8;
case 0xFF52: return _state.CgbDmaSource & 0xFF;
case 0xFF53: return _state.CgbDmaDest >> 8;
case 0xFF54: return _state.CgbDmaDest & 0xFF;
case 0xFF55: return _state.CgbDmaLength | (_state.CgbHdmaMode ? 0x80 : 0);
case 0xFF68: return _state.CgbBgPalPosition | (_state.CgbBgPalAutoInc ? 0x80 : 0);
case 0xFF69: return (_state.CgbBgPalettes[_state.CgbBgPalPosition >> 1] >> ((_state.CgbBgPalPosition & 0x01) ? 8 : 0) & 0xFF);
case 0xFF6A: return _state.CgbObjPalPosition | (_state.CgbObjPalAutoInc ? 0x80 : 0);
case 0xFF6B: return (_state.CgbObjPalettes[_state.CgbObjPalPosition >> 1] >> ((_state.CgbObjPalPosition & 0x01) ? 8 : 0) & 0xFF);
}
LogDebug("[Debug] GBC - Missing read handler: $" + HexUtilities::ToHex(addr));
return 0;
}
void GbPpu::WriteCgbRegister(uint16_t addr, uint8_t value)
{
switch(addr) {
case 0xFF4F: _state.CgbVramBank = value & 0x01; break;
case 0xFF51: _state.CgbDmaSource = (_state.CgbDmaSource & 0xFF) | (value << 8); break;
case 0xFF52: _state.CgbDmaSource = (_state.CgbDmaSource & 0xFF00) | value; break;
case 0xFF53: _state.CgbDmaDest = (_state.CgbDmaDest & 0xFF) | (value << 8); break;
case 0xFF54: _state.CgbDmaDest = (_state.CgbDmaDest & 0xFF00) | value; break;
case 0xFF55:
_state.CgbDmaLength = value & 0x7F;
_state.CgbHdmaMode = (value & 0x80) != 0;
if(!_state.CgbHdmaMode) {
//TODO check invalid dma sources/etc.
for(int i = 0; i < _state.CgbDmaLength * 16; i++) {
WriteVram((_state.CgbDmaDest & 0xFFF0) + i, _memoryManager->Read((_state.CgbDmaSource & 0xFFF0) + i, MemoryOperationType::DmaRead));
}
_state.CgbDmaLength = 0x7F;
} else {
MessageManager::Log("TODO HDMA");
}
break;
case 0xFF68:
//FF68 - BCPS/BGPI - CGB Mode Only - Background Palette Index
_state.CgbBgPalPosition = value & 0x3F;
_state.CgbBgPalAutoInc = (value & 0x80) != 0;
break;
case 0xFF69: {
//FF69 - BCPD/BGPD - CGB Mode Only - Background Palette Data
WriteCgbPalette(_state.CgbBgPalPosition, _state.CgbBgPalettes, _state.CgbBgPalAutoInc, value);
break;
}
case 0xFF6A:
//FF6A - OCPS/OBPI - CGB Mode Only - Sprite Palette Index
_state.CgbObjPalPosition = value & 0x3F;
_state.CgbObjPalAutoInc = (value & 0x80) != 0;
break;
case 0xFF6B:
//FF6B - OCPD/OBPD - CGB Mode Only - Sprite Palette Data
WriteCgbPalette(_state.CgbObjPalPosition, _state.CgbObjPalettes, _state.CgbObjPalAutoInc, value);
break;
default:
LogDebug("[Debug] GBC - Missing write handler: $" + HexUtilities::ToHex(addr));
break;
}
}
void GbPpu::WriteCgbPalette(uint8_t& pos, uint16_t* pal, bool autoInc, uint8_t value)
{
if((int)_state.Mode <= (int)PpuMode::OamEvaluation) {
if(pos & 0x01) {
pal[pos >> 1] = (pal[pos >> 1] & 0xFF) | ((value & 0x7F) << 8);
} else {
pal[pos >> 1] = (pal[pos >> 1] & 0xFF00) | value;
}
}
if(autoInc) {
pos = (pos + 1) & 0x3F;
}
}
void GbPpu::Serialize(Serializer& s)
{
s.Stream(
_state.Scanline, _state.Cycle, _state.Mode, _state.LyCompare, _state.BgPalette, _state.ObjPalette0, _state.ObjPalette1,
_state.ScrollX, _state.ScrollY, _state.WindowX, _state.WindowY, _state.Control, _state.LcdEnabled, _state.WindowTilemapSelect,
_state.WindowEnabled, _state.BgTileSelect, _state.BgTilemapSelect, _state.LargeSprites, _state.SpritesEnabled, _state.BgEnabled,
_state.Status, _state.FrameCount, _lastFrameTime
_state.Status, _state.FrameCount, _lastFrameTime,
_state.CgbBgPalAutoInc, _state.CgbBgPalPosition, _state.CgbDmaDest, _state.CgbDmaLength, _state.CgbDmaSource, _state.CgbHdmaMode,
_state.CgbObjPalAutoInc, _state.CgbObjPalPosition, _state.CgbVramBank
);
s.StreamArray(_state.CgbBgPalettes, 4 * 8);
s.StreamArray(_state.CgbObjPalettes, 4 * 8);
}

View file

@ -24,6 +24,11 @@ private:
void ExecCycle();
void RenderScanline();
template<bool isCgb>
void RenderScanline();
void WriteCgbPalette(uint8_t& pos, uint16_t* pal, bool autoInc, uint8_t value);
public:
virtual ~GbPpu();
@ -46,5 +51,8 @@ public:
uint8_t ReadOam(uint8_t addr);
void WriteOam(uint8_t addr, uint8_t value);
uint8_t ReadCgbRegister(uint16_t addr);
void WriteCgbRegister(uint16_t addr, uint8_t value);
void Serialize(Serializer& s) override;
};

View file

@ -25,7 +25,8 @@ void GbTimer::Exec()
}
}
if(!(newValue & 0x1000) && (_divider & 0x1000)) {
uint16_t frameSeqBit = _memoryManager->IsHighSpeed() ? 0x2000 : 0x1000;
if(!(newValue & frameSeqBit) && (_divider & frameSeqBit)) {
_apu->ClockFrameSequencer();
}

View file

@ -128,6 +128,20 @@ struct GbPpuState
uint8_t Status;
uint32_t FrameCount;
uint8_t CgbVramBank;
uint16_t CgbDmaSource;
uint16_t CgbDmaDest;
uint8_t CgbDmaLength;
bool CgbHdmaMode;
uint8_t CgbBgPalPosition;
bool CgbBgPalAutoInc;
uint16_t CgbBgPalettes[4 * 8];
uint8_t CgbObjPalPosition;
bool CgbObjPalAutoInc;
uint16_t CgbObjPalettes[4 * 8];
};
struct GbSquareState
@ -249,6 +263,10 @@ enum class GbMemoryType
struct GbMemoryManagerState
{
uint8_t CgbWorkRamBank;
bool CgbSwitchSpeedRequest;
bool CgbHighSpeed;
uint64_t ApuCycleCount;
bool DisableBootRom;
uint8_t IrqRequests;
uint8_t IrqEnabled;
@ -262,8 +280,15 @@ struct GbMemoryManagerState
RegisterAccess MemoryAccessType[0x100];
};
enum class GbType
{
Gb = 0,
Cgb = 1,
};
struct GbState
{
GbType Type;
GbCpuState Cpu;
GbPpuState Ppu;
GbApuDebugState Apu;

View file

@ -378,30 +378,46 @@ void PpuTools::UpdateViewers(uint16_t scanline, uint16_t cycle)
void PpuTools::GetGameboyTilemap(uint8_t* vram, uint16_t offset, uint32_t* outBuffer)
{
GbPpu* ppu = _console->GetCartridge()->GetGameboy()->GetPpu();
GbPpuState state = ppu->GetState();
Gameboy* gameboy = _console->GetCartridge()->GetGameboy();
GbState state = gameboy->GetState();
bool isCgb = state.Type == GbType::Cgb;
uint16_t palette[4];
ppu->GetPalette(palette, state.BgPalette);
gameboy->GetPpu()->GetPalette(palette, state.Ppu.BgPalette);
uint16_t baseTile = state.BgTileSelect ? 0 : 0x1000;
uint16_t baseTile = state.Ppu.BgTileSelect ? 0 : 0x1000;
std::fill(outBuffer, outBuffer + 1024*256, 0xFFFFFFFF);
uint16_t vramMask = isCgb ? 0x3FFF : 0x1FFF;
for(int row = 0; row < 32; row++) {
uint16_t baseOffset = offset + ((row & 0x1F) << 5);
for(int column = 0; column < 32; column++) {
uint16_t addr = (baseOffset + column);
uint8_t tileIndex = vram[addr];
uint16_t tileStart = baseTile + (baseTile ? (int8_t)tileIndex*16 : tileIndex*16);
for(int y = 0; y < 8; y++) {
uint16_t pixelStart = tileStart + y * 2;
for(int x = 0; x < 8; x++) {
uint8_t shift = 7 - (x & 0x07);
uint8_t color = GetTilePixelColor(vram, 0x1FFF, 2, pixelStart, shift);
if(color != 0) {
uint8_t attributes = isCgb ? vram[addr | 0x2000] : 0;
uint8_t bgPalette = (attributes & 0x07) << 2;
uint16_t tileBank = (attributes & 0x08) ? 0x2000 : 0x0000;
bool hMirror = (attributes & 0x20) != 0;
bool vMirror = (attributes & 0x40) != 0;
//bool bgPriority = (attributes & 0x80) != 0;
uint16_t tileStart = baseTile + (baseTile ? (int8_t)tileIndex*16 : tileIndex*16);
tileStart |= tileBank;
for(int y = 0; y < 8; y++) {
uint16_t pixelStart = tileStart + (vMirror ? (7 - y) : y) * 2;
for(int x = 0; x < 8; x++) {
uint8_t shift = hMirror ? (x & 0x07) : (7 - (x & 0x07));
uint8_t color = GetTilePixelColor(vram, vramMask, 2, pixelStart, shift);
if(isCgb) {
outBuffer[((row * 8) + y) * 1024 + column * 8 + x] = DefaultVideoFilter::ToArgb(state.Ppu.CgbBgPalettes[bgPalette + color]);
} else if(color != 0) {
outBuffer[((row * 8) + y) * 1024 + column * 8 + x] = DefaultVideoFilter::ToArgb(palette[color]);
}
}

View file

@ -126,28 +126,40 @@ namespace Mesen.GUI.Debugger
byte[] cgRam = new byte[512];
//Generate a fake SNES-like palette based on the gameboy PPU state
GbPpuState state = DebugApi.GetState().Gameboy.Ppu;
GbState state = DebugApi.GetState().Gameboy;
GbPpuState ppu = state.Ppu;
byte[,] paletteBytes = new byte[4,2] {
{ 0xFF, 0x7F}, {0x18,0x63}, {0x8C, 0x31}, {0,0}
};
if(state.Type == GbType.Cgb) {
for(int i = 0; i < 8 * 4; i++) {
cgRam[i * 2] = (byte)(ppu.CgbBgPalettes[i] & 0xFF);
cgRam[i * 2 + 1] = (byte)(ppu.CgbBgPalettes[i] >> 8);
}
Action<byte, UInt16> setPalette = (byte pal, UInt16 offset) => {
cgRam[offset] = paletteBytes[pal & 0x03, 0];
cgRam[offset+1] = paletteBytes[pal & 0x03, 1];
cgRam[offset+2] = paletteBytes[(pal >> 2) & 0x03, 0];
cgRam[offset+3] = paletteBytes[(pal >> 2) & 0x03, 1];
cgRam[offset+4] = paletteBytes[(pal >> 4) & 0x03, 0];
cgRam[offset+5] = paletteBytes[(pal >> 4) & 0x03, 1];
cgRam[offset+6] = paletteBytes[(pal >> 6) & 0x03, 0];
cgRam[offset+7] = paletteBytes[(pal >> 6) & 0x03, 1];
};
for(int i = 0; i < 8 * 4; i++) {
cgRam[128 + i * 2] = (byte)(ppu.CgbObjPalettes[i] & 0xFF);
cgRam[128 + i * 2 + 1] = (byte)(ppu.CgbObjPalettes[i] >> 8);
}
} else {
byte[,] paletteBytes = new byte[4,2] {
{ 0xFF, 0x7F}, {0x18,0x63}, {0x8C, 0x31}, {0,0}
};
setPalette(state.BgPalette, 0);
setPalette(state.ObjPalette0, 32);
setPalette(state.ObjPalette1, 64);
setPalette(0xE4, 96);
Action<byte, UInt16> setPalette = (byte pal, UInt16 offset) => {
cgRam[offset] = paletteBytes[pal & 0x03, 0];
cgRam[offset+1] = paletteBytes[pal & 0x03, 1];
cgRam[offset+2] = paletteBytes[(pal >> 2) & 0x03, 0];
cgRam[offset+3] = paletteBytes[(pal >> 2) & 0x03, 1];
cgRam[offset+4] = paletteBytes[(pal >> 4) & 0x03, 0];
cgRam[offset+5] = paletteBytes[(pal >> 4) & 0x03, 1];
cgRam[offset+6] = paletteBytes[(pal >> 6) & 0x03, 0];
cgRam[offset+7] = paletteBytes[(pal >> 6) & 0x03, 1];
};
setPalette(ppu.BgPalette, 0);
setPalette(ppu.ObjPalette0, 32);
setPalette(ppu.ObjPalette1, 64);
setPalette(0xE4, 96);
}
return cgRam;
}

View file

@ -320,6 +320,8 @@ namespace Mesen.GUI.Debugger
}
cboMemoryType.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.GbVideoRam));
cboMemoryType.Items.Add("-");
cboMemoryType.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.GameboyMemory));
cboMemoryType.Items.Add("-");
cboMemoryType.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.GbPrgRom));
cboMemoryType.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.GbWorkRam));
cboMemoryType.Items.Add(ResourceHelper.GetEnumText(SnesMemoryType.GbCartRam));

View file

@ -625,6 +625,11 @@ namespace Mesen.GUI
public struct GbMemoryManagerState
{
public byte CgbWorkRamBank;
[MarshalAs(UnmanagedType.I1)] public bool CgbSwitchSpeedRequest;
[MarshalAs(UnmanagedType.I1)] public bool CgbHighSpeed;
public UInt64 ApuCycleCount;
[MarshalAs(UnmanagedType.I1)] public bool DisableBootRom;
public byte IrqRequests;
public byte IrqEnabled;
@ -646,8 +651,15 @@ namespace Mesen.GUI
public RegisterAccess[] MemoryAccessType;
}
public enum GbType
{
Gb = 0,
Cgb = 1,
}
public struct GbState
{
public GbType Type;
public GbCpuState Cpu;
public GbPpuState Ppu;
public GbApuDebugState Apu;
@ -720,6 +732,24 @@ namespace Mesen.GUI
public byte Status;
public UInt32 FrameCount;
public byte CgbVramBank;
public UInt16 CgbDmaSource;
public UInt16 CgbDmaDest;
public byte CgbDmaLength;
[MarshalAs(UnmanagedType.I1)] public bool CgbHdmaMode;
public byte CgbBgPalPosition;
[MarshalAs(UnmanagedType.I1)] public bool CgbBgPalAutoInc;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4*8)]
public UInt16[] CgbBgPalettes;
public byte CgbObjPalPosition;
[MarshalAs(UnmanagedType.I1)] public bool CgbObjPalAutoInc;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4 * 8)]
public UInt16[] CgbObjPalettes;
}
public struct GbSquareState

View file

@ -15,7 +15,7 @@ namespace Mesen.GUI.Utilities
public static bool IsRomFile(string path)
{
string ext = Path.GetExtension(path).ToLower();
return ext == ".sfc" || ext == ".smc" || ext == ".fig" || ext == ".swc" || ext == ".bs" || ext == ".gb";
return ext == ".sfc" || ext == ".smc" || ext == ".fig" || ext == ".swc" || ext == ".bs" || ext == ".gb" || ext == ".gbc";
}
public static bool IsArchiveFile(string path)

View file

@ -10,7 +10,7 @@
#include "../Utilities/IpsPatcher.h"
#include "../Utilities/UpsPatcher.h"
const std::initializer_list<string> VirtualFile::RomExtensions = { ".sfc", ".smc", ".swc", ".fig", ".bs", ".gb" };
const std::initializer_list<string> VirtualFile::RomExtensions = { ".sfc", ".smc", ".swc", ".fig", ".bs", ".gb", ".gbc" };
VirtualFile::VirtualFile()
{