Mesen2/Core/Gameboy/Gameboy.cpp

684 lines
18 KiB
C++

#include "pch.h"
#include "Gameboy/Gameboy.h"
#include "Gameboy/GbCpu.h"
#include "Gameboy/GbPpu.h"
#include "Gameboy/APU/GbApu.h"
#include "Gameboy/Carts/GbCart.h"
#include "Gameboy/GbTimer.h"
#include "Gameboy/GbControlManager.h"
#include "Gameboy/GbDmaController.h"
#include "Gameboy/GbMemoryManager.h"
#include "Gameboy/GbCartFactory.h"
#include "Gameboy/GameboyHeader.h"
#include "Gameboy/GbsHeader.h"
#include "Gameboy/Carts/GbsCart.h"
#include "Gameboy/GbBootRom.h"
#include "Gameboy/GbDefaultVideoFilter.h"
#include "Gameboy/GbxFooter.h"
#include "Debugger/DebugTypes.h"
#include "Shared/CheatManager.h"
#include "Shared/BatteryManager.h"
#include "Shared/Audio/AudioPlayerTypes.h"
#include "Shared/BaseControlManager.h"
#include "Shared/EmuSettings.h"
#include "Shared/MessageManager.h"
#include "Shared/FirmwareHelper.h"
#include "Utilities/VirtualFile.h"
#include "Utilities/Serializer.h"
#include "Utilities/CRC32.h"
Gameboy::Gameboy(Emulator* emu, bool allowSgb)
{
_emu = emu;
_allowSgb = allowSgb;
}
Gameboy::~Gameboy()
{
delete[] _cartRam;
delete[] _prgRom;
delete[] _spriteRam;
delete[] _videoRam;
delete[] _highRam;
delete[] _workRam;
delete[] _bootRom;
}
void Gameboy::Init(GbCart* cart, std::vector<uint8_t>& romData, uint32_t cartRamSize, bool hasBattery)
{
_cart.reset(cart);
_ppu.reset(new GbPpu());
_apu.reset(new GbApu());
_cpu.reset(new GbCpu());
_memoryManager.reset(new GbMemoryManager());
_timer.reset(new GbTimer());
_dmaController.reset(new GbDmaController());
_controlManager.reset(new GbControlManager(_emu, this));
_prgRomSize = (uint32_t)romData.size();
_prgRom = new uint8_t[_prgRomSize];
memcpy(_prgRom, romData.data(), romData.size());
_emu->RegisterMemory(MemoryType::GbPrgRom, _prgRom, _prgRomSize);
_cartRamSize = cartRamSize;
_cartRam = new uint8_t[_cartRamSize];
_emu->RegisterMemory(MemoryType::GbCartRam, _cartRam, _cartRamSize);
_hasBattery = hasBattery;
bool cgbMode = _model == GameboyModel::GameboyColor;
_workRamSize = cgbMode ? 0x8000 : 0x2000;
_videoRamSize = cgbMode ? 0x4000 : 0x2000;
_workRam = new uint8_t[_workRamSize];
_emu->RegisterMemory(MemoryType::GbWorkRam, _workRam, _workRamSize);
_videoRam = new uint8_t[_videoRamSize];
_emu->RegisterMemory(MemoryType::GbVideoRam, _videoRam, _videoRamSize);
_spriteRam = new uint8_t[Gameboy::SpriteRamSize];
_emu->RegisterMemory(MemoryType::GbSpriteRam, _spriteRam, Gameboy::SpriteRamSize);
_highRam = new uint8_t[Gameboy::HighRamSize];
_emu->RegisterMemory(MemoryType::GbHighRam, _highRam, Gameboy::HighRamSize);
_bootRomSize = 0;
EmuSettings* settings = _emu->GetSettings();
GameboyConfig cfg = settings->GetGameboyConfig();
FirmwareType type = FirmwareType::Gameboy;
if(_model == GameboyModel::SuperGameboy) {
type = cfg.UseSgb2 ? FirmwareType::Sgb2GameboyCpu : FirmwareType::Sgb1GameboyCpu;
} else if(_model == GameboyModel::GameboyColor) {
type = FirmwareType::GameboyColor;
}
_bootRomSize = cgbMode ? 9 * 256 : 256;
if(GetRomFormat() == RomFormat::Gbs || !FirmwareHelper::LoadGbBootRom(_emu, &_bootRom, type)) {
switch(_model) {
default:
case GameboyModel::Gameboy:
_bootRom = new uint8_t[_bootRomSize];
memcpy(_bootRom, dmgBootRom, _bootRomSize);
break;
case GameboyModel::GameboyColor:
_bootRom = new uint8_t[_bootRomSize];
memcpy(_bootRom, cgbBootRom, _bootRomSize);
break;
case GameboyModel::SuperGameboy:
_bootRom = new uint8_t[_bootRomSize];
if(cfg.UseSgb2) {
memcpy(_bootRom, sgb2BootRom, _bootRomSize);
} else {
memcpy(_bootRom, sgbBootRom, _bootRomSize);
}
break;
}
}
_emu->RegisterMemory(MemoryType::GbBootRom, _bootRom, _bootRomSize);
InitializeRam(_cartRam, _cartRamSize);
InitializeRam(_workRam, _workRamSize);
InitializeRam(_spriteRam, Gameboy::SpriteRamSize);
InitializeRam(_highRam, Gameboy::HighRamSize);
InitializeRam(_videoRam, _videoRamSize);
LoadBattery();
if(!_allowSgb) {
PowerOn(nullptr);
}
}
void Gameboy::PowerOn(SuperGameboy *sgb)
{
_superGameboy = sgb;
_timer->Init(_memoryManager.get(), _apu.get());
_apu->Init(_emu, this);
_cart->Init(this, _memoryManager.get());
_memoryManager->Init(_emu, this, _cart.get(), _ppu.get(), _apu.get(), _timer.get(), _dmaController.get());
_cpu->Init(_emu, this, _memoryManager.get());
_ppu->Init(_emu, this, _memoryManager.get(), _dmaController.get(), _videoRam, _spriteRam);
_dmaController->Init(this, _memoryManager.get(), _ppu.get(), _cpu.get());
_cpu->PowerOn();
}
void Gameboy::Run(uint64_t runUntilClock)
{
while(_cpu->GetCycleCount() < runUntilClock) {
_cpu->Exec();
}
}
void Gameboy::LoadBattery()
{
if(_hasBattery) {
_emu->GetBatteryManager()->LoadBattery(".srm", _cartRam, _cartRamSize);
}
}
void Gameboy::SaveBattery()
{
if(_hasBattery) {
_emu->GetBatteryManager()->SaveBattery(".srm", _cartRam, _cartRamSize);
}
}
GbState Gameboy::GetState()
{
GbState state;
state.Type = IsCgb() ? GbType::Cgb : GbType::Gb;
state.Cpu = _cpu->GetState();
state.Ppu = _ppu->GetState();
state.Apu = _apu->GetState();
state.MemoryManager = _memoryManager->GetState();
state.ControlManager = _controlManager->GetState();
state.Dma = _dmaController->GetState();
state.Timer = _timer->GetState();
state.HasBattery = _hasBattery;
return state;
}
void Gameboy::GetConsoleState(BaseState& state, ConsoleType consoleType)
{
(GbState&)state = GetState();
}
uint32_t Gameboy::DebugGetMemorySize(MemoryType type)
{
switch(type) {
case MemoryType::GbPrgRom: return _prgRomSize;
case MemoryType::GbWorkRam: return _workRamSize;
case MemoryType::GbCartRam: return _cartRamSize;
case MemoryType::GbHighRam: return Gameboy::HighRamSize;
case MemoryType::GbBootRom: return _bootRomSize;
case MemoryType::GbVideoRam: return _videoRamSize;
case MemoryType::GbSpriteRam: return Gameboy::SpriteRamSize;
default: return 0;
}
}
uint8_t* Gameboy::DebugGetMemory(MemoryType type)
{
switch(type) {
case MemoryType::GbPrgRom: return _prgRom;
case MemoryType::GbWorkRam: return _workRam;
case MemoryType::GbCartRam: return _cartRam;
case MemoryType::GbHighRam: return _highRam;
case MemoryType::GbBootRom: return _bootRom;
case MemoryType::GbVideoRam: return _videoRam;
case MemoryType::GbSpriteRam: return _spriteRam;
default: return nullptr;
}
}
GbMemoryManager* Gameboy::GetMemoryManager()
{
return _memoryManager.get();
}
Emulator* Gameboy::GetEmulator()
{
return _emu;
}
GbPpu* Gameboy::GetPpu()
{
return _ppu.get();
}
GbCpu* Gameboy::GetCpu()
{
return _cpu.get();
}
GbTimer* Gameboy::GetTimer()
{
return _timer.get();
}
void Gameboy::GetSoundSamples(int16_t* &samples, uint32_t& sampleCount)
{
_apu->GetSoundSamples(samples, sampleCount);
}
AddressInfo Gameboy::GetAbsoluteAddress(uint16_t addr)
{
AddressInfo addrInfo = { -1, MemoryType::None };
if(addr >= 0xFF80 && addr <= 0xFFFE) {
addrInfo.Address = addr & 0x7F;
addrInfo.Type = MemoryType::GbHighRam;
return addrInfo;
} else if(addr >= 0xFE00 && addr < 0xFF00) {
addrInfo.Address = addr & 0x7F;
addrInfo.Type = MemoryType::GbSpriteRam;
return addrInfo;
} else if(addr >= 0xFF00) {
//Return empty for registers at >= $FF00 (needed to prevent UI from showing work ram addresses)
return addrInfo;
} else if(addr >= 0x8000 && addr <= 0x9FFF) {
addrInfo.Address = (addr & 0x1FFF) | (_ppu->GetStateRef().CgbVramBank << 13);
addrInfo.Type = MemoryType::GbVideoRam;
return addrInfo;
}
uint8_t* ptr = _memoryManager->GetMappedBlock(addr);
if(!ptr) {
return addrInfo;
}
ptr += (addr & 0xFF);
if(ptr >= _prgRom && ptr < _prgRom + _prgRomSize) {
addrInfo.Address = (int32_t)(ptr - _prgRom);
addrInfo.Type = MemoryType::GbPrgRom;
} else if(ptr >= _workRam && ptr < _workRam + _workRamSize) {
addrInfo.Address = (int32_t)(ptr - _workRam);
addrInfo.Type = MemoryType::GbWorkRam;
} else if(ptr >= _cartRam && ptr < _cartRam + _cartRamSize) {
addrInfo.Address = (int32_t)(ptr - _cartRam);
addrInfo.Type = MemoryType::GbCartRam;
} else if(ptr >= _bootRom && ptr < _bootRom + _bootRomSize) {
addrInfo.Address = (int32_t)(ptr - _bootRom);
addrInfo.Type = MemoryType::GbBootRom;
}
return addrInfo;
}
int32_t Gameboy::GetRelativeAddress(AddressInfo& absAddress)
{
if(absAddress.Type == MemoryType::GbHighRam) {
return 0xFF80 | (absAddress.Address & 0x7F);
}
for(int32_t i = 0; i < 0x10000; i += 0x100) {
AddressInfo blockAddr = GetAbsoluteAddress(i);
if(blockAddr.Type == absAddress.Type && (blockAddr.Address & ~0xFF) == (absAddress.Address & ~0xFF)) {
return i | (absAddress.Address & 0xFF);
}
}
return -1;
}
bool Gameboy::IsCpuStopped()
{
return _cpu->GetState().Stopped;
}
bool Gameboy::IsCgb()
{
return _model == GameboyModel::GameboyColor;
}
bool Gameboy::IsSgb()
{
return _model == GameboyModel::SuperGameboy;
}
SuperGameboy* Gameboy::GetSgb()
{
return _superGameboy;
}
uint64_t Gameboy::GetCycleCount()
{
return _cpu->GetCycleCount();
}
uint64_t Gameboy::GetApuCycleCount()
{
return _memoryManager->GetApuCycleCount();
}
void Gameboy::Serialize(Serializer& s)
{
SV(_cpu);
SV(_ppu);
SV(_apu);
SV(_cart); //Process cart before memory manager to ensure mappings are updated properly
SV(_memoryManager);
SV(_timer);
SV(_dmaController);
SV(_hasBattery);
SVArray(_cartRam, _cartRamSize);
SVArray(_workRam, _workRamSize);
SVArray(_videoRam, _videoRamSize);
SVArray(_spriteRam, Gameboy::SpriteRamSize);
SVArray(_highRam, Gameboy::HighRamSize);
SV(_controlManager);
}
SaveStateCompatInfo Gameboy::ValidateSaveStateCompatibility(ConsoleType stateConsoleType)
{
if(stateConsoleType == ConsoleType::Snes) {
return { true, "", "cart.gameboy." };
}
return {};
}
void Gameboy::Reset()
{
//The GB has no reset button, behave like power cycle
_emu->ReloadRom(true);
}
LoadRomResult Gameboy::LoadRom(VirtualFile& romFile)
{
vector<uint8_t> romData;
romFile.ReadFile(romData);
if(romData.size() < Gameboy::HeaderOffset + sizeof(GameboyHeader)) {
return LoadRomResult::Failure;
}
GbsHeader gbsHeader = {};
memcpy(&gbsHeader, romData.data(), sizeof(GbsHeader));
if(!_allowSgb && memcmp(gbsHeader.Header, "GBS", sizeof(gbsHeader.Header)) == 0) {
//GBS music file
uint16_t loadAddr = gbsHeader.LoadAddress[0] | (gbsHeader.LoadAddress[1] << 8);
//Pad start with 0s until load address
vector<uint8_t> gbsRomData = vector<uint8_t>(loadAddr, 0);
gbsRomData.insert(gbsRomData.end(), romData.begin() + sizeof(GbsHeader), romData.end());
if((gbsRomData.size() & 0x3FFF) != 0) {
//Pad to multiple of 16kb
gbsRomData.insert(gbsRomData.end(), 0x4000 - (gbsRomData.size() & 0x3FFF), 0);
}
MessageManager::Log("-----------------------------");
MessageManager::Log("File: " + romFile.GetFileName());
GbsCart* cart = new GbsCart(gbsHeader);
_model = GameboyModel::GameboyColor;
Init(cart, gbsRomData, 0x5000, false);
cart->InitPlayback(gbsHeader.FirstTrack - 1);
return LoadRomResult::Success;
} else {
GbxFooter gbxFooter = {};
gbxFooter.Init(romData);
if((romData.size() & 0x3FFF) != 0) {
//Pad to multiple of 16kb
romData.insert(romData.end(), 0x4000 - (romData.size() & 0x3FFF), 0);
}
GameboyHeader header = GetHeader(romData.data(), (uint32_t)romData.size());
_model = GetEffectiveModel(header);
if(_allowSgb && _model != GameboyModel::SuperGameboy) {
return LoadRomResult::UnknownType;
}
MessageManager::Log("-----------------------------");
MessageManager::Log("File: " + romFile.GetFileName());
MessageManager::Log("Game: " + header.GetCartName());
MessageManager::Log("Cart Type: " + std::to_string(header.CartType));
switch((CgbCompat)((int)header.CgbFlag & 0xC0)) {
case CgbCompat::Gameboy: MessageManager::Log("Supports: Game Boy"); break;
case CgbCompat::GameboyColorSupport: MessageManager::Log("Supports: Game Boy Color (compatible with GB)"); break;
case CgbCompat::GameboyColorExclusive: MessageManager::Log("Supports: Game Boy Color only"); break;
}
if(header.SgbFlag == 0x03) {
MessageManager::Log("Supports: Super Game Boy");
}
MessageManager::Log("File size: " + std::to_string(romData.size() / 1024) + " KB");
if(header.GetCartRamSize() > 0) {
string sizeString = header.GetCartRamSize() > 1024 ? std::to_string(header.GetCartRamSize() / 1024) + " KB" : std::to_string(header.GetCartRamSize()) + " bytes";
MessageManager::Log("Cart RAM size: " + sizeString + (header.HasBattery() ? " (with battery)" : ""));
}
GbCart* cart = GbCartFactory::CreateCart(_emu, header, gbxFooter, romData);
MessageManager::Log("-----------------------------");
if(cart) {
if(gbxFooter.IsValid()) {
Init(cart, romData, gbxFooter.GetRamSize(), gbxFooter.HasBattery());
} else {
Init(cart, romData, header.GetCartRamSize(), header.HasBattery());
}
return LoadRomResult::Success;
} else {
MessageManager::DisplayMessage("Error", "Unsupported cart type: " + (gbxFooter.IsValid() ? gbxFooter.GetMapperId() : std::to_string(header.CartType)));
return LoadRomResult::Failure;
}
}
return LoadRomResult::UnknownType;
}
GameboyHeader Gameboy::GetHeader(uint8_t* romData, uint32_t romSize)
{
GameboyHeader header = {};
memcpy(&header, romData + Gameboy::HeaderOffset, sizeof(GameboyHeader));
if(romSize > 0x8000) {
uint32_t logoPosition = (uint32_t)(romSize - 0x8000 + 0x104);
if(CRC32::GetCRC(&romData[logoPosition], 0x30) == 0x46195417) {
//Found logo at the end of the rom, use this header instead
//MMM01 games have the header here because of their default mappings at power on
int offset = (int)(romSize - 0x8000 + Gameboy::HeaderOffset);
memcpy(&header, romData + offset, sizeof(GameboyHeader));
uint8_t headerChecksum = 0;
for(int i = 0; i < sizeof(GameboyHeader) - 3; i++) {
headerChecksum = headerChecksum - romData[offset + i] - 1;
}
if(header.CartType == 0x11) {
//Mani 4-in-1 has carttype set as $11, but is actually a MMM01 cart
header.CartType = 0x0B;
}
if(headerChecksum != header.HeaderChecksum) {
//Invalid header, ignore it and use the default header location instead
memcpy(&header, romData + Gameboy::HeaderOffset, sizeof(GameboyHeader));
}
}
}
return header;
}
GameboyHeader Gameboy::GetHeader()
{
return GetHeader(_prgRom, _prgRomSize);
}
GameboyModel Gameboy::GetEffectiveModel(GameboyHeader& header)
{
EmuSettings* settings = _emu->GetSettings();
GameboyConfig cfg = settings->GetGameboyConfig();
GameboyModel model = cfg.Model;
CgbCompat cgbFlag = (CgbCompat)((int)header.CgbFlag & 0xC0);
bool supportsSgb = header.SgbFlag == 0x03;
switch(model) {
case GameboyModel::AutoFavorGb:
if(cgbFlag == CgbCompat::GameboyColorExclusive) {
model = GameboyModel::GameboyColor;
} else {
model = GameboyModel::Gameboy;
}
break;
case GameboyModel::AutoFavorSgb:
if(cgbFlag == CgbCompat::GameboyColorExclusive) {
model = GameboyModel::GameboyColor;
} else {
model = GameboyModel::SuperGameboy;
}
break;
case GameboyModel::AutoFavorGbc:
if(supportsSgb && cgbFlag == CgbCompat::Gameboy) {
model = GameboyModel::SuperGameboy;
} else {
model = GameboyModel::GameboyColor;
}
break;
}
if(!_allowSgb && model == GameboyModel::SuperGameboy) {
//SGB isn't available, use gameboy color mode instead
model = GameboyModel::GameboyColor;
}
return model;
}
void Gameboy::RunFrame()
{
uint32_t frameCount = _ppu->GetFrameCount();
while(frameCount == _ppu->GetFrameCount()) {
_cpu->Exec();
}
_apu->Run();
_apu->PlayQueuedAudio();
}
void Gameboy::ProcessEndOfFrame()
{
_controlManager->UpdateControlDevices();
_controlManager->UpdateInputState();
}
BaseControlManager* Gameboy::GetControlManager()
{
return _controlManager.get();
}
ConsoleType Gameboy::GetConsoleType()
{
return ConsoleType::Gameboy;
}
double Gameboy::GetFps()
{
return 59.72750056960583;
}
PpuFrameInfo Gameboy::GetPpuFrame()
{
PpuFrameInfo frame;
frame.FrameBuffer = (uint8_t*)_ppu->GetOutputBuffer();
frame.FrameCount = _ppu->GetFrameCount();
frame.Width = 160;
frame.Height = 144;
frame.FrameBufferSize = frame.Width * frame.Height * sizeof(uint16_t);
frame.FirstScanline = 0;
frame.ScanlineCount = 154;
frame.CycleCount = 456;
return frame;
}
vector<CpuType> Gameboy::GetCpuTypes()
{
return { CpuType::Gameboy };
}
AddressInfo Gameboy::GetAbsoluteAddress(AddressInfo& relAddress)
{
return GetAbsoluteAddress(relAddress.Address);
}
AddressInfo Gameboy::GetRelativeAddress(AddressInfo& absAddress, CpuType cpuType)
{
return { GetRelativeAddress(absAddress), MemoryType::GameboyMemory };
}
uint64_t Gameboy::GetMasterClock()
{
return _cpu->GetCycleCount();
}
uint32_t Gameboy::GetMasterClockRate()
{
return _memoryManager->IsHighSpeed() ? 4194304*2 : 4194304;
}
BaseVideoFilter* Gameboy::GetVideoFilter(bool getDefaultFilter)
{
if(getDefaultFilter || GetRomFormat() == RomFormat::Gbs) {
return new GbDefaultVideoFilter(_emu, false);
}
VideoFilterType filterType = _emu->GetSettings()->GetVideoConfig().VideoFilter;
switch(filterType) {
case VideoFilterType::NtscBlargg:
case VideoFilterType::NtscBisqwit:
return new GbDefaultVideoFilter(_emu, true);
default:
return new GbDefaultVideoFilter(_emu, false);
}
}
RomFormat Gameboy::GetRomFormat()
{
return dynamic_cast<GbsCart*>(_cart.get()) ? RomFormat::Gbs : RomFormat::Gb;
}
AudioTrackInfo Gameboy::GetAudioTrackInfo()
{
GbsCart* cart = dynamic_cast<GbsCart*>(_cart.get());
if(cart) {
return cart->GetAudioTrackInfo();
}
return {};
}
void Gameboy::ProcessAudioPlayerAction(AudioPlayerActionParams p)
{
GbsCart* cart = dynamic_cast<GbsCart*>(_cart.get());
if(cart) {
cart->ProcessAudioPlayerAction(p);
}
}
void Gameboy::InitGbsPlayback(uint8_t selectedTrack)
{
GbsCart* cart = dynamic_cast<GbsCart*>(_cart.get());
if(cart) {
cart->InitPlayback(selectedTrack);
}
}
ConsoleRegion Gameboy::GetRegion()
{
return ConsoleRegion::Ntsc;
}
void Gameboy::RefreshRamCheats()
{
//Used to refresh gameshark codes when vertical blank IRQ is triggered
_emu->GetCheatManager()->RefreshRamCheats(CpuType::Gameboy);
for(InternalCheatCode& code : _emu->GetCheatManager()->GetRamRefreshCheats(CpuType::Gameboy)) {
if(!code.IsAbsoluteAddress) {
_memoryManager->DebugWrite(code.Address, code.Value);
}
}
}
void Gameboy::InitializeRam(void* data, uint32_t length)
{
EmuSettings* settings = _emu->GetSettings();
settings->InitializeRam(settings->GetGameboyConfig().RamPowerOnState, data, length);
}