Added basic support for GBC games
This commit is contained in:
parent
5f055110fa
commit
371a7a1ac8
19 changed files with 453 additions and 133 deletions
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
173
Core/GbPpu.cpp
173
Core/GbPpu.cpp
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
Loading…
Add table
Reference in a new issue