410 lines
9.8 KiB
C++
410 lines
9.8 KiB
C++
#include "stdafx.h"
|
|
#include "Console.h"
|
|
#include "Gameboy.h"
|
|
#include "GbCpu.h"
|
|
#include "GbPpu.h"
|
|
#include "GbApu.h"
|
|
#include "GbCart.h"
|
|
#include "GbTimer.h"
|
|
#include "GbDmaController.h"
|
|
#include "DebugTypes.h"
|
|
#include "GbMemoryManager.h"
|
|
#include "GbCartFactory.h"
|
|
#include "BatteryManager.h"
|
|
#include "GameboyHeader.h"
|
|
#include "EmuSettings.h"
|
|
#include "MessageManager.h"
|
|
#include "FirmwareHelper.h"
|
|
#include "GbBootRom.h"
|
|
#include "../Utilities/VirtualFile.h"
|
|
#include "../Utilities/Serializer.h"
|
|
|
|
Gameboy* Gameboy::Create(Console* console, VirtualFile& romFile, bool sgbEnabled)
|
|
{
|
|
vector<uint8_t> romData;
|
|
romFile.ReadFile(romData);
|
|
|
|
GameboyHeader header;
|
|
memcpy(&header, romData.data() + Gameboy::HeaderOffset, sizeof(GameboyHeader));
|
|
|
|
MessageManager::Log("-----------------------------");
|
|
MessageManager::Log("File: " + romFile.GetFileName());
|
|
MessageManager::Log("Game: " + header.GetCartName());
|
|
MessageManager::Log("Cart Type: " + std::to_string(header.CartType));
|
|
switch (header.CgbFlag & 0xC0)
|
|
{
|
|
case 0x00: MessageManager::Log("Supports: Game Boy");
|
|
break;
|
|
case 0x80: MessageManager::Log("Supports: Game Boy Color (compatible with GB)");
|
|
break;
|
|
case 0xC0: MessageManager::Log("Supports: Game Boy Color only");
|
|
break;
|
|
}
|
|
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)" : ""));
|
|
}
|
|
MessageManager::Log("-----------------------------");
|
|
|
|
GbCart* cart = GbCartFactory::CreateCart(header.CartType);
|
|
|
|
if (cart)
|
|
{
|
|
Gameboy* gb = new Gameboy();
|
|
gb->Init(console, cart, romData, header, sgbEnabled);
|
|
return gb;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void Gameboy::Init(Console* console, GbCart* cart, std::vector<uint8_t>& romData, GameboyHeader& header,
|
|
bool sgbEnabled)
|
|
{
|
|
_console = console;
|
|
_cart.reset(cart);
|
|
|
|
_prgRomSize = (uint32_t)romData.size();
|
|
_prgRom = new uint8_t[_prgRomSize];
|
|
memcpy(_prgRom, romData.data(), romData.size());
|
|
|
|
_cartRamSize = header.GetCartRamSize();
|
|
_cartRam = new uint8_t[_cartRamSize];
|
|
_hasBattery = header.HasBattery();
|
|
|
|
shared_ptr<EmuSettings> settings = console->GetSettings();
|
|
GameboyConfig cfg = settings->GetGameboyConfig();
|
|
GameboyModel model = cfg.Model;
|
|
if (model == GameboyModel::Auto)
|
|
{
|
|
if ((header.CgbFlag & 0x80) != 0)
|
|
{
|
|
model = GameboyModel::GameboyColor;
|
|
}
|
|
else
|
|
{
|
|
model = GameboyModel::SuperGameboy;
|
|
}
|
|
}
|
|
|
|
if (!sgbEnabled && model == GameboyModel::SuperGameboy)
|
|
{
|
|
//SGB bios isn't available, use gameboy color mode instead
|
|
model = GameboyModel::GameboyColor;
|
|
}
|
|
|
|
_model = model;
|
|
|
|
bool cgbMode = _model == GameboyModel::GameboyColor;
|
|
_workRamSize = cgbMode ? 0x8000 : 0x2000;
|
|
_videoRamSize = cgbMode ? 0x4000 : 0x2000;
|
|
|
|
_workRam = new uint8_t[_workRamSize];
|
|
_videoRam = new uint8_t[_videoRamSize];
|
|
_spriteRam = new uint8_t[Gameboy::SpriteRamSize];
|
|
_highRam = new uint8_t[Gameboy::HighRamSize];
|
|
|
|
_bootRomSize = 0;
|
|
|
|
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 (!FirmwareHelper::LoadGbBootRom(console, &_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;
|
|
}
|
|
}
|
|
|
|
settings->InitializeRam(_cartRam, _cartRamSize);
|
|
settings->InitializeRam(_workRam, _workRamSize);
|
|
settings->InitializeRam(_spriteRam, Gameboy::SpriteRamSize);
|
|
settings->InitializeRam(_highRam, Gameboy::HighRamSize);
|
|
settings->InitializeRam(_videoRam, _videoRamSize);
|
|
|
|
LoadBattery();
|
|
}
|
|
|
|
Gameboy::Gameboy()
|
|
{
|
|
_ppu.reset(new GbPpu());
|
|
_apu.reset(new GbApu());
|
|
_cpu.reset(new GbCpu());
|
|
_memoryManager.reset(new GbMemoryManager());
|
|
_timer.reset(new GbTimer());
|
|
_dmaController.reset(new GbDmaController());
|
|
}
|
|
|
|
Gameboy::~Gameboy()
|
|
{
|
|
SaveBattery();
|
|
|
|
delete[] _cartRam;
|
|
delete[] _prgRom;
|
|
|
|
delete[] _spriteRam;
|
|
delete[] _videoRam;
|
|
|
|
delete[] _highRam;
|
|
delete[] _workRam;
|
|
|
|
delete[] _bootRom;
|
|
}
|
|
|
|
void Gameboy::PowerOn(SuperGameboy* superGameboy)
|
|
{
|
|
_superGameboy = superGameboy;
|
|
|
|
_timer->Init(_memoryManager.get(), _apu.get());
|
|
_apu->Init(_console, this);
|
|
_cart->Init(this, _memoryManager.get());
|
|
_memoryManager->Init(_console, this, _cart.get(), _ppu.get(), _apu.get(), _timer.get(), _dmaController.get());
|
|
_cpu->Init(_console, this, _memoryManager.get());
|
|
_ppu->Init(_console, this, _memoryManager.get(), _dmaController.get(), _videoRam, _spriteRam);
|
|
_dmaController->Init(_memoryManager.get(), _ppu.get(), _cpu.get());
|
|
}
|
|
|
|
void Gameboy::Exec()
|
|
{
|
|
_cpu->Exec();
|
|
}
|
|
|
|
void Gameboy::Run(uint64_t runUntilClock)
|
|
{
|
|
while (_memoryManager->GetCycleCount() < runUntilClock)
|
|
{
|
|
_cpu->Exec();
|
|
}
|
|
}
|
|
|
|
void Gameboy::LoadBattery()
|
|
{
|
|
if (_hasBattery)
|
|
{
|
|
_console->GetBatteryManager()->LoadBattery(".srm", _cartRam, _cartRamSize);
|
|
}
|
|
}
|
|
|
|
void Gameboy::SaveBattery()
|
|
{
|
|
if (_hasBattery)
|
|
{
|
|
_console->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.Dma = _dmaController->GetState();
|
|
state.Timer = _timer->GetState();
|
|
state.HasBattery = _hasBattery;
|
|
return state;
|
|
}
|
|
|
|
uint32_t Gameboy::DebugGetMemorySize(SnesMemoryType type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case SnesMemoryType::GbPrgRom: return _prgRomSize;
|
|
case SnesMemoryType::GbWorkRam: return _workRamSize;
|
|
case SnesMemoryType::GbCartRam: return _cartRamSize;
|
|
case SnesMemoryType::GbHighRam: return Gameboy::HighRamSize;
|
|
case SnesMemoryType::GbBootRom: return _bootRomSize;
|
|
case SnesMemoryType::GbVideoRam: return _videoRamSize;
|
|
case SnesMemoryType::GbSpriteRam: return Gameboy::SpriteRamSize;
|
|
default: return 0;
|
|
}
|
|
}
|
|
|
|
uint8_t* Gameboy::DebugGetMemory(SnesMemoryType type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case SnesMemoryType::GbPrgRom: return _prgRom;
|
|
case SnesMemoryType::GbWorkRam: return _workRam;
|
|
case SnesMemoryType::GbCartRam: return _cartRam;
|
|
case SnesMemoryType::GbHighRam: return _highRam;
|
|
case SnesMemoryType::GbBootRom: return _bootRom;
|
|
case SnesMemoryType::GbVideoRam: return _videoRam;
|
|
case SnesMemoryType::GbSpriteRam: return _spriteRam;
|
|
default: return nullptr;
|
|
}
|
|
}
|
|
|
|
GbMemoryManager* Gameboy::GetMemoryManager()
|
|
{
|
|
return _memoryManager.get();
|
|
}
|
|
|
|
GbPpu* Gameboy::GetPpu()
|
|
{
|
|
return _ppu.get();
|
|
}
|
|
|
|
GbCpu* Gameboy::GetCpu()
|
|
{
|
|
return _cpu.get();
|
|
}
|
|
|
|
void Gameboy::GetSoundSamples(int16_t* & samples, uint32_t& sampleCount)
|
|
{
|
|
_apu->GetSoundSamples(samples, sampleCount);
|
|
}
|
|
|
|
AddressInfo Gameboy::GetAbsoluteAddress(uint16_t addr)
|
|
{
|
|
AddressInfo addrInfo = {-1, SnesMemoryType::Register};
|
|
|
|
if (addr >= 0xFF80 && addr <= 0xFFFE)
|
|
{
|
|
addrInfo.Address = addr & 0x7F;
|
|
addrInfo.Type = SnesMemoryType::GbHighRam;
|
|
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 = SnesMemoryType::GbPrgRom;
|
|
}
|
|
else if (ptr >= _workRam && ptr < _workRam + _workRamSize)
|
|
{
|
|
addrInfo.Address = (int32_t)(ptr - _workRam);
|
|
addrInfo.Type = SnesMemoryType::GbWorkRam;
|
|
}
|
|
else if (ptr >= _cartRam && ptr < _cartRam + _cartRamSize)
|
|
{
|
|
addrInfo.Address = (int32_t)(ptr - _cartRam);
|
|
addrInfo.Type = SnesMemoryType::GbCartRam;
|
|
}
|
|
else if (ptr >= _bootRom && ptr < _bootRom + _bootRomSize)
|
|
{
|
|
addrInfo.Address = (int32_t)(ptr - _bootRom);
|
|
addrInfo.Type = SnesMemoryType::GbBootRom;
|
|
}
|
|
return addrInfo;
|
|
}
|
|
|
|
int32_t Gameboy::GetRelativeAddress(AddressInfo& absAddress)
|
|
{
|
|
if (absAddress.Type == SnesMemoryType::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;
|
|
}
|
|
|
|
GameboyHeader Gameboy::GetHeader()
|
|
{
|
|
GameboyHeader header;
|
|
memcpy(&header, _prgRom + Gameboy::HeaderOffset, sizeof(GameboyHeader));
|
|
return header;
|
|
}
|
|
|
|
bool Gameboy::IsCgb()
|
|
{
|
|
return _model == GameboyModel::GameboyColor;
|
|
}
|
|
|
|
bool Gameboy::IsSgb()
|
|
{
|
|
return _model == GameboyModel::SuperGameboy;
|
|
}
|
|
|
|
SuperGameboy* Gameboy::GetSgb()
|
|
{
|
|
return _superGameboy;
|
|
}
|
|
|
|
uint64_t Gameboy::GetCycleCount()
|
|
{
|
|
return _memoryManager->GetCycleCount();
|
|
}
|
|
|
|
uint64_t Gameboy::GetApuCycleCount()
|
|
{
|
|
return _memoryManager->GetApuCycleCount();
|
|
}
|
|
|
|
void Gameboy::Serialize(Serializer& s)
|
|
{
|
|
s.Stream(_cpu.get());
|
|
s.Stream(_ppu.get());
|
|
s.Stream(_apu.get());
|
|
s.Stream(_memoryManager.get());
|
|
s.Stream(_cart.get());
|
|
s.Stream(_timer.get());
|
|
s.Stream(_dmaController.get());
|
|
s.Stream(_hasBattery);
|
|
|
|
s.StreamArray(_cartRam, _cartRamSize);
|
|
s.StreamArray(_workRam, _workRamSize);
|
|
s.StreamArray(_videoRam, _videoRamSize);
|
|
s.StreamArray(_spriteRam, Gameboy::SpriteRamSize);
|
|
s.StreamArray(_highRam, Gameboy::HighRamSize);
|
|
}
|
|
|
|
void Gameboy::SetReg(GbRegister reg, uint16_t value)
|
|
{
|
|
_cpu->SetReg(reg, value);
|
|
}
|