mirror of
https://github.com/SourMesen/Mesen2.git
synced 2025-04-02 10:21:44 -04:00
GBS file support (WIP)
This commit is contained in:
parent
ffad810480
commit
667782647e
11 changed files with 248 additions and 32 deletions
|
@ -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" />
|
||||
|
|
|
@ -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">
|
||||
|
|
137
Core/Gameboy/Carts/GbsCart.h
Normal file
137
Core/Gameboy/Carts/GbsCart.h
Normal 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);
|
||||
}
|
||||
};
|
|
@ -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()
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
18
Core/Gameboy/GbsHeader.h
Normal 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];
|
||||
};
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -111,6 +111,7 @@ public:
|
|||
void RunSingleFrame();
|
||||
void Stop(bool sendNotification);
|
||||
|
||||
void OnBeforeSendFrame();
|
||||
void ProcessEndOfFrame();
|
||||
|
||||
void Reset();
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Add table
Reference in a new issue