GBS file support (WIP)

This commit is contained in:
Sour 2021-05-06 21:35:09 -04:00
parent ffad810480
commit 667782647e
11 changed files with 248 additions and 32 deletions

View file

@ -44,6 +44,8 @@
</ItemGroup>
<ItemGroup>
<ClInclude Include="Debugger\stdafx.h" />
<ClInclude Include="Gameboy\Carts\GbsCart.h" />
<ClInclude Include="Gameboy\GbsHeader.h" />
<ClInclude Include="NES\Input\ArkanoidController.h" />
<ClInclude Include="NES\Input\AsciiTurboFile.h" />
<ClInclude Include="NES\Input\BandaiHyperShot.h" />

View file

@ -781,6 +781,8 @@
<ClInclude Include="NES\NesConstants.h" />
<ClInclude Include="Shared\Audio\AudioPlayerTypes.h" />
<ClInclude Include="Shared\Video\SystemHud.h" />
<ClInclude Include="Gameboy\GbsHeader.h" />
<ClInclude Include="Gameboy\Carts\GbsCart.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="SNES\Cpu.cpp">

View file

@ -0,0 +1,137 @@
#pragma once
#include "stdafx.h"
#include "Gameboy/Carts/GbCart.h"
#include "Gameboy/Gameboy.h"
#include "Gameboy/GbMemoryManager.h"
#include "Shared/Audio/AudioPlayerTypes.h"
#include "Shared/Emulator.h"
#include "Shared/SystemActionManager.h"
#include "Utilities/Serializer.h"
class GbsCart : public GbCart
{
private:
uint8_t _prgBank = 1;
uint16_t _currentTrack = 0;
GbsHeader _header;
public:
GbsCart(GbsHeader header)
{
_header = header;
_currentTrack = header.FirstTrack - 1;
}
void InitCart() override
{
_memoryManager->MapRegisters(0x2000, 0x3FFF, RegisterAccess::Write);
}
void InitPlayback(uint16_t selectedTrack)
{
_currentTrack = selectedTrack;
uint8_t* prg = _gameboy->DebugGetMemory(SnesMemoryType::GbPrgRom);
//Clear high ram and cart ram
memset(_cartRam, 0, _gameboy->DebugGetMemorySize(SnesMemoryType::GbCartRam));
memset(_gameboy->DebugGetMemory(SnesMemoryType::GbHighRam), 0, _gameboy->DebugGetMemorySize(SnesMemoryType::GbHighRam));
memset(_gameboy->DebugGetMemory(SnesMemoryType::GbWorkRam), 0, _gameboy->DebugGetMemorySize(SnesMemoryType::GbWorkRam));
//Patch ROM to call INIT and PLAY routines
//CALL [INIT]
prg[0] = 0xCD;
prg[1] = _header.InitAddress[0];
prg[2] = _header.InitAddress[1];
//Infinite loop (JP $0003)
prg[3] = 0xC3;
prg[4] = 0x03;
prg[5] = 0x00;
//CALL [PLAY]
prg[0x50] = 0xCD;
prg[0x51] = _header.PlayAddress[0];
prg[0x52] = _header.PlayAddress[1];
//RETI
prg[0x53] = 0xD9;
GbCpuState& state = _gameboy->GetCpu()->GetState();
state = {};
state.SP = _header.StackPointer[0] | (_header.StackPointer[1] << 8);
state.PC = 0;
state.A = selectedTrack;
state.IME = true; //enable CPU interrupts
//Disable boot room
_memoryManager->WriteRegister(0xFF50, 0x01);
//Turn on PPU for vblank interrupts
_memoryManager->WriteRegister(0xFF40, 0x80);
//Enable all IRQs
_memoryManager->WriteRegister(0xFFFF, 0xFF);
//Enable APU
_memoryManager->WriteRegister(0xFF26, 0x80);
_memoryManager->WriteRegister(0xFF25, 0xFF);
//Clear all IRQ requests (needed when switching tracks)
_memoryManager->ClearIrqRequest(0xFF);
_prgBank = 1;
RefreshMappings();
}
AudioTrackInfo GetAudioTrackInfo()
{
AudioTrackInfo info = {};
info.GameTitle = string(_header.Title, 32);
info.Artist = string(_header.Author, 32);
info.Comment = string(_header.Copyright, 32);
info.TrackNumber = _currentTrack + 1;
info.TrackCount = _header.TrackCount;
info.Position = (double)_gameboy->GetMasterClock() / _gameboy->GetMasterClockRate();
return info;
}
void ProcessAudioPlayerAction(AudioPlayerActionParams p)
{
int selectedTrack = _currentTrack;
switch(p.Action) {
case AudioPlayerAction::NextTrack: selectedTrack++; break;
case AudioPlayerAction::PrevTrack: selectedTrack--; break;
case AudioPlayerAction::SelectTrack: selectedTrack = (int)p.TrackNumber; break;
}
if(selectedTrack < 0) {
selectedTrack = _header.TrackCount - 1;
} else if(selectedTrack >= _header.TrackCount) {
selectedTrack = 0;
}
auto lock = _gameboy->GetEmulator()->AcquireLock();
InitPlayback(selectedTrack);
}
void RefreshMappings() override
{
constexpr int prgBankSize = 0x4000;
Map(0x0000, 0x3FFF, GbMemoryType::PrgRom, 0, true);
Map(0x4000, 0x7FFF, GbMemoryType::PrgRom, _prgBank * prgBankSize, true);
Map(0xA000, 0xFFFF, GbMemoryType::CartRam, 0, false);
}
void WriteRegister(uint16_t addr, uint8_t value) override
{
_prgBank = std::max<uint8_t>(1, value);
RefreshMappings();
}
void Serialize(Serializer& s) override
{
s.Stream(_prgBank, _currentTrack);
}
};

View file

@ -10,9 +10,12 @@
#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 "Debugger/DebugTypes.h"
#include "Shared/BatteryManager.h"
#include "Shared/Audio/AudioPlayerTypes.h"
#include "Shared/EmuSettings.h"
#include "Shared/MessageManager.h"
#include "Utilities/VirtualFile.h"
@ -43,7 +46,7 @@ Gameboy::~Gameboy()
delete[] _bootRom;
}
void Gameboy::Init(GbCart* cart, std::vector<uint8_t>& romData, GameboyHeader& header)
void Gameboy::Init(GbCart* cart, std::vector<uint8_t>& romData, uint32_t cartRamSize, bool hasBattery, bool supportsCgb)
{
_cart.reset(cart);
@ -60,17 +63,17 @@ void Gameboy::Init(GbCart* cart, std::vector<uint8_t>& romData, GameboyHeader& h
memcpy(_prgRom, romData.data(), romData.size());
_emu->RegisterMemory(SnesMemoryType::GbPrgRom, _prgRom, _prgRomSize);
_cartRamSize = header.GetCartRamSize();
_cartRamSize = cartRamSize;
_cartRam = new uint8_t[_cartRamSize];
_emu->RegisterMemory(SnesMemoryType::GbCartRam, _cartRam, _cartRamSize);
_hasBattery = header.HasBattery();
_hasBattery = hasBattery;
EmuSettings* settings = _emu->GetSettings();
GameboyConfig cfg = settings->GetGameboyConfig();
GameboyModel model = cfg.Model;
if(model == GameboyModel::Auto) {
if((header.CgbFlag & 0x80) != 0) {
if(supportsCgb) {
model = GameboyModel::GameboyColor;
} else {
model = GameboyModel::SuperGameboy;
@ -110,7 +113,7 @@ void Gameboy::Init(GbCart* cart, std::vector<uint8_t>& romData, GameboyHeader& h
}
_bootRomSize = cgbMode ? 9 * 256 : 256;
if(!FirmwareHelper::LoadGbBootRom(_emu, &_bootRom, type)) {
if(GetRomFormat() == RomFormat::Gbs || !FirmwareHelper::LoadGbBootRom(_emu, &_bootRom, type)) {
switch(_model) {
default:
case GameboyModel::Gameboy:
@ -229,6 +232,11 @@ GbMemoryManager* Gameboy::GetMemoryManager()
return _memoryManager.get();
}
Emulator* Gameboy::GetEmulator()
{
return _emu;
}
GbPpu* Gameboy::GetPpu()
{
return _ppu.get();
@ -363,31 +371,56 @@ bool Gameboy::LoadRom(VirtualFile& romFile)
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;
if(romData.size() < Gameboy::HeaderOffset + sizeof(GameboyHeader)) {
return false;
}
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("-----------------------------");
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);
GbCart* cart = GbCartFactory::CreateCart(header.CartType);
//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);
}
GbsCart* cart = new GbsCart(gbsHeader);
Init(cart, gbsRomData, 0x5000, false, false);
cart->InitPlayback(gbsHeader.FirstTrack - 1);
if(cart) {
Init(cart, romData, header);
return true;
} else {
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) {
Init(cart, romData, header.GetCartRamSize(), header.HasBattery(), (header.CgbFlag & 0x80) != false);
return true;
}
}
return false;
@ -403,7 +436,6 @@ void Gameboy::RunFrame()
while(frameCount == _ppu->GetFrameCount()) {
_cpu->Exec();
}
_emu->ProcessEndOfFrame();
}
void Gameboy::ProcessEndOfFrame()
@ -468,7 +500,8 @@ uint64_t Gameboy::GetMasterClock()
uint32_t Gameboy::GetMasterClockRate()
{
return 20971520;
//TODO GBC
return 4194304;
}
BaseVideoFilter* Gameboy::GetVideoFilter()
@ -484,16 +517,24 @@ BaseVideoFilter* Gameboy::GetVideoFilter()
RomFormat Gameboy::GetRomFormat()
{
return RomFormat::Gb;
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);
}
}
ConsoleRegion Gameboy::GetRegion()

View file

@ -60,13 +60,14 @@ private:
uint8_t* _bootRom = nullptr;
uint32_t _bootRomSize = 0;
void Init(GbCart* cart, std::vector<uint8_t>& romData, uint32_t cartRamSize, bool hasBattery, bool supportsCgb);
public:
static constexpr int HeaderOffset = 0x134;
Gameboy(Emulator* emu, bool allowSgb);
virtual ~Gameboy();
void Init(GbCart* cart, std::vector<uint8_t>& romData, GameboyHeader& header);
void PowerOn(SuperGameboy* sgb);
void Run(uint64_t runUntilClock);
@ -74,6 +75,8 @@ public:
void LoadBattery();
void SaveBattery();
Emulator* GetEmulator();
GbPpu* GetPpu();
GbCpu* GetCpu();
void GetSoundSamples(int16_t* &samples, uint32_t& sampleCount);

View file

@ -651,6 +651,8 @@ void GbPpu::SendFrame()
_emu->GetVideoDecoder()->UpdateFrame(_currentBuffer, 256, 239, _state.FrameCount, rewinding, rewinding);
#endif
_emu->ProcessEndOfFrame();
//TODO move this somewhere that makes more sense
uint8_t prevInput = _memoryManager->ReadInputPort();
_gameboy->ProcessEndOfFrame();

18
Core/Gameboy/GbsHeader.h Normal file
View file

@ -0,0 +1,18 @@
#include "stdafx.h"
struct GbsHeader
{
char Header[3];
uint8_t Version;
uint8_t TrackCount;
uint8_t FirstTrack;
uint8_t LoadAddress[2];
uint8_t InitAddress[2];
uint8_t PlayAddress[2];
uint8_t StackPointer[2];
uint8_t TimerModulo;
uint8_t TimerControl;
char Title[32];
char Author[32];
char Copyright[32];
};

View file

@ -1198,7 +1198,6 @@ void NesPpu::SendFrame()
UpdateGrayscaleAndIntensifyBits();
if(_console->IsVsMainConsole()) {
_emu->ProcessEndOfFrame();
_emu->GetNotificationManager()->SendNotification(ConsoleNotificationType::PpuFrameDone, _currentOutputBuffer);
}
@ -1207,9 +1206,13 @@ void NesPpu::SendFrame()
#else
if(_console->GetVsMainConsole() || _console->GetVsSubConsole()) {
SendFrameVsDualSystem();
if(_console->IsVsMainConsole()) {
_emu->ProcessEndOfFrame();
}
} else {
bool forRewind = _emu->GetRewindManager()->IsRewinding();
_emu->GetVideoDecoder()->UpdateFrame(_currentOutputBuffer, NesConstants::ScreenWidth, NesConstants::ScreenHeight, _frameCount, forRewind, forRewind);
_emu->ProcessEndOfFrame();
}
_enableOamDecay = _settings->GetNesConfig().EnableOamDecay;

View file

@ -191,14 +191,19 @@ void Emulator::RunFrameWithRunAhead()
}
}
void Emulator::ProcessEndOfFrame()
void Emulator::OnBeforeSendFrame()
{
#ifndef LIBRETRO
if(!_isRunAheadFrame) {
if(_audioPlayerHud) {
_audioPlayerHud->Draw();
}
}
}
void Emulator::ProcessEndOfFrame()
{
#ifndef LIBRETRO
if(!_isRunAheadFrame) {
_frameLimiter->ProcessFrame();
while(_frameLimiter->WaitForNextFrame()) {
if(_stopFlag || _frameDelay != GetFrameDelay() || _paused || _pauseOnNextFrame || _lockCounter > 0) {

View file

@ -111,6 +111,7 @@ public:
void RunSingleFrame();
void Stop(bool sendNotification);
void OnBeforeSendFrame();
void ProcessEndOfFrame();
void Reset();

View file

@ -145,6 +145,8 @@ void VideoDecoder::UpdateFrame(uint16_t *ppuOutputBuffer, uint16_t width, uint16
return;
}
_emu->OnBeforeSendFrame();
if(_frameChanged) {
//Last frame isn't done decoding yet - sometimes Signal() introduces a 25-30ms delay
while(_frameChanged) {