Mesen-S/Core/SuperGameboy.cpp
2020-06-24 19:20:55 -04:00

363 lines
9.4 KiB
C++

#include "stdafx.h"
#include "SuperGameboy.h"
#include "Console.h"
#include "MemoryManager.h"
#include "EmuSettings.h"
#include "BaseCartridge.h"
#include "Spc.h"
#include "Gameboy.h"
#include "GbApu.h"
#include "GbPpu.h"
#include "MessageManager.h"
#include "../Utilities/HexUtilities.h"
#include "../Utilities/HermiteResampler.h"
SuperGameboy::SuperGameboy(Console* console) : BaseCoprocessor(SnesMemoryType::Register)
{
_mixBuffer = new int16_t[0x10000];
_console = console;
_memoryManager = console->GetMemoryManager().get();
_cart = _console->GetCartridge().get();
_spc = _console->GetSpc().get();
_gameboy = _cart->GetGameboy();
_ppu = _gameboy->GetPpu();
_control = 0x01; //Divider = 5, gameboy = not running
UpdateClockRatio();
MemoryMappings* cpuMappings = _memoryManager->GetMemoryMappings();
for(int i = 0; i <= 0x3F; i++) {
cpuMappings->RegisterHandler(i, i, 0x6000, 0x7FFF, this);
cpuMappings->RegisterHandler(i + 0x80, i + 0x80, 0x6000, 0x7FFF, this);
}
_gameboy->PowerOn(this);
}
SuperGameboy::~SuperGameboy()
{
delete[] _mixBuffer;
}
void SuperGameboy::Reset()
{
_control = 0;
_resetClock = 0;
memset(_input, 0, sizeof(_input));
_inputIndex = 0;
_listeningForPacket = false;
_waitForHigh = true;
_packetReady = false;
_inputWriteClock = 0;
_inputValue = 0;
memset(_packetData, 0, sizeof(_packetData));
_packetByte = 0;
_packetBit = 0;
_lcdRowSelect = 0;
_readPosition = 0;
memset(_lcdBuffer, 0, sizeof(_lcdBuffer));
}
uint8_t SuperGameboy::Read(uint32_t addr)
{
addr &= 0xF80F;
if(addr >= 0x7000 && addr <= 0x700F) {
_packetReady = false;
return _packetData[addr & 0x0F];
} else if(addr >= 0x7800 && addr <= 0x780F) {
if(_readPosition >= 320) {
//Return 0xFF for 320..511 and then wrap to 0
_readPosition = (_readPosition + 1) & 0x1FF;
return 0xFF;
}
uint8_t* start = _lcdBuffer[_lcdRowSelect];
start += ((_readPosition >> 1) & 0x07) * 160;
start += (_readPosition >> 4) * 8;
uint8_t data = 0;
uint8_t shift = _readPosition & 0x01;
for(int i = 0; i < 8; i++) {
data |= ((start[i] >> shift) & 0x01) << (7 - i);
}
_readPosition++;
return data;
} else {
switch(addr & 0xFFFF) {
case 0x6000: return (GetLcdRow() << 3) | GetLcdBufferRow();
case 0x6002: return _packetReady;
case 0x600F: return 0x21; //or 0x61
}
}
return 0;
}
void SuperGameboy::Write(uint32_t addr, uint8_t value)
{
addr &= 0xF80F;
switch(addr & 0xFFFF) {
case 0x6001:
_lcdRowSelect = value & 0x03;
_readPosition = 0;
break;
case 0x6003: {
if(!(_control & 0x80) && (value & 0x80)) {
_resetClock = _memoryManager->GetMasterClock();
_gameboy->PowerOn(this);
_ppu = _gameboy->GetPpu();
}
_control = value;
_inputIndex %= GetPlayerCount();
UpdateClockRatio();
break;
}
case 0x6004: _input[0] = value; break;
case 0x6005: _input[1] = value; break;
case 0x6006: _input[2] = value; break;
case 0x6007: _input[3] = value; break;
}
}
void SuperGameboy::ProcessInputPortWrite(uint8_t value)
{
if(_inputValue == value) {
return;
}
if(value == 0x00) {
//Reset pulse
_waitForHigh = true;
_packetByte = 0;
_packetBit = 0;
} else if(_waitForHigh) {
if(value == 0x10 || value == 0x20) {
//Invalid sequence (should be 0x00 -> 0x30 -> 0x10/0x20 -> 0x30 -> 0x10/0x20, etc.)
_waitForHigh = false;
_listeningForPacket = false;
} else if(value == 0x30) {
_waitForHigh = false;
_listeningForPacket = true;
}
} else if(_listeningForPacket) {
if(value == 0x20) {
//0 bit
if(_packetByte >= 16 && _packetBit == 0) {
_packetReady = true;
_listeningForPacket = false;
if(_console->IsDebugging()) {
LogPacket();
}
} else {
_packetData[_packetByte] &= ~(1 << _packetBit);
}
_packetBit++;
if(_packetBit == 8) {
_packetBit = 0;
_packetByte++;
}
} else if(value == 0x10) {
//1 bit
if(_packetByte >= 16) {
//Invalid bit
_listeningForPacket = false;
} else {
_packetData[_packetByte] |= (1 << _packetBit);
_packetBit++;
if(_packetBit == 8) {
_packetBit = 0;
_packetByte++;
}
}
}
_waitForHigh = _listeningForPacket;
} else if(!(_inputValue & 0x20) && (value & 0x20)) {
_inputIndex = (_inputIndex + 1) % GetPlayerCount();
}
_inputValue = value;
_inputWriteClock = _memoryManager->GetMasterClock();
}
void SuperGameboy::LogPacket()
{
uint8_t commandId = _packetData[0] >> 3;
string name;
switch(commandId) {
case 0: name = "PAL01"; break; //Set SGB Palette 0, 1 Data
case 1: name = "PAL23"; break; //Set SGB Palette 2, 3 Data
case 2: name = "PAL03"; break; //Set SGB Palette 0, 3 Data
case 3: name = "PAL12"; break; //Set SGB Palette 1, 2 Data
case 4: name = "ATTR_BLK"; break; //"Block" Area Designation Mode
case 5: name = "ATTR_LIN"; break; //"Line" Area Designation Mode
case 6: name = "ATTR_DIV"; break; //"Divide" Area Designation Mode
case 7: name = "ATTR_CHR"; break; //"1CHR" Area Designation Mode
case 8: name = "SOUND"; break; //Sound On / Off
case 9: name = "SOU_TRN"; break; //Transfer Sound PRG / DATA
case 0xA: name = "PAL_SET"; break; //Set SGB Palette Indirect
case 0xB: name = "PAL_TRN"; break; //Set System Color Palette Data
case 0xC: name = "ATRC_EN"; break; //Enable / disable Attraction Mode
case 0xD: name = "TEST_EN"; break; //Speed Function
case 0xE: name = "ICON_EN"; break; //SGB Function
case 0xF: name = "DATA_SND"; break; //SUPER NES WRAM Transfer 1
case 0x10: name = "DATA_TRN"; break; //SUPER NES WRAM Transfer 2
case 0x11: name = "MLT_REG"; break; //Controller 2 Request
case 0x12: name = "JUMP"; break; //Set SNES Program Counter
case 0x13: name = "CHR_TRN"; break; //Transfer Character Font Data
case 0x14: name = "PCT_TRN"; break; //Set Screen Data Color Data
case 0x15: name = "ATTR_TRN"; break; //Set Attribute from ATF
case 0x16: name = "ATTR_SET"; break; //Set Data to ATF
case 0x17: name = "MASK_EN"; break; //Game Boy Window Mask
case 0x18: name = "OBJ_TRN"; break; //Super NES OBJ Mode
case 0x1E: name = "Header Data"; break;
case 0x1F: name = "Header Data"; break;
default: name = "Unknown"; break;
}
string log = "SGB Command: " + HexUtilities::ToHex(commandId) + " - " + name + " (Len: " + std::to_string(_packetData[0] & 0x07) + ") - ";
for(int i = 0; i < 16; i++) {
log += HexUtilities::ToHex(_packetData[i]) + " ";
}
_console->DebugLog(log);
}
void SuperGameboy::WriteLcdColor(uint8_t scanline, uint8_t pixel, uint8_t color)
{
_lcdBuffer[GetLcdBufferRow()][(scanline & 0x07) * 160 + pixel] = color;
}
uint8_t SuperGameboy::GetLcdRow()
{
uint8_t scanline = _ppu->GetScanline();
uint8_t row = scanline / 8;
if(row >= 18) {
row = 0;
}
return row;
}
uint8_t SuperGameboy::GetLcdBufferRow()
{
return (_ppu->GetFrameCount() * 18 + GetLcdRow()) & 0x03;
}
uint8_t SuperGameboy::GetPlayerCount()
{
uint8_t playerCount = ((_control >> 4) & 0x03) + 1;
if(playerCount >= 3) {
//Unknown: 2 and 3 both mean 4 players?
return 4;
}
return playerCount;
}
void SuperGameboy::MixAudio(uint32_t targetRate, int16_t* soundSamples, uint32_t sampleCount)
{
int16_t* gbSamples = nullptr;
uint32_t gbSampleCount = 0;
_gameboy->GetSoundSamples(gbSamples, gbSampleCount);
_resampler.SetSampleRates(GbApu::SampleRate, targetRate);
int32_t outCount = (int32_t)_resampler.Resample(gbSamples, gbSampleCount, _mixBuffer + _mixSampleCount) * 2;
_mixSampleCount += outCount;
int32_t copyCount = (int32_t)std::min(_mixSampleCount, sampleCount*2);
if(!_spc->IsMuted()) {
for(int32_t i = 0; i < copyCount; i++) {
soundSamples[i] += _mixBuffer[i];
}
}
int32_t remainingSamples = (int32_t)_mixSampleCount - copyCount;
if(remainingSamples > 0) {
memmove(_mixBuffer, _mixBuffer + copyCount, remainingSamples*sizeof(int16_t));
_mixSampleCount = remainingSamples;
} else {
_mixSampleCount = 0;
}
}
void SuperGameboy::Run()
{
if(!(_control & 0x80)) {
return;
}
_gameboy->Run((uint64_t)((_memoryManager->GetMasterClock() - _resetClock) * _clockRatio));
}
void SuperGameboy::UpdateClockRatio()
{
bool isSgb2 = _console->GetSettings()->GetGameboyConfig().UseSgb2;
uint32_t masterRate = isSgb2 ? 20971520 : _console->GetMasterClockRate();
uint8_t divider = 5;
//TODO: This doesn't actually work properly if the speed is changed while the SGB is running (but this most likely never happens?)
switch(_control & 0x03) {
case 0: divider = 4; break;
case 1: divider = 5; break;
case 2: divider = 7; break;
case 3: divider = 9; break;
}
double effectiveRate = (double)masterRate / divider;
_clockRatio = effectiveRate / _console->GetMasterClockRate();
}
uint32_t SuperGameboy::GetClockRate()
{
return (uint32_t)(_console->GetMasterClockRate() * _clockRatio);
}
uint8_t SuperGameboy::GetInputIndex()
{
return 0xF - _inputIndex;
}
uint8_t SuperGameboy::GetInput()
{
return _input[_inputIndex];
}
uint8_t SuperGameboy::Peek(uint32_t addr)
{
return 0;
}
void SuperGameboy::PeekBlock(uint32_t addr, uint8_t* output)
{
memset(output, 0, 0x1000);
}
AddressInfo SuperGameboy::GetAbsoluteAddress(uint32_t address)
{
return { -1, SnesMemoryType::Register };
}
void SuperGameboy::Serialize(Serializer& s)
{
s.Stream(
_control, _resetClock, _input[0], _input[1], _input[2], _input[3], _inputIndex, _listeningForPacket, _packetReady,
_inputWriteClock, _inputValue, _packetByte, _packetBit, _lcdRowSelect, _readPosition, _waitForHigh, _clockRatio
);
s.StreamArray(_packetData, 16);
s.StreamArray(_lcdBuffer[0], 1280);
s.StreamArray(_lcdBuffer[1], 1280);
s.StreamArray(_lcdBuffer[2], 1280);
s.StreamArray(_lcdBuffer[3], 1280);
}