Mesen2/Core/SMS/SmsConsole.cpp

359 lines
9 KiB
C++

#include "pch.h"
#include "SMS/SmsConsole.h"
#include "SMS/SmsCpu.h"
#include "SMS/SmsControlManager.h"
#include "SMS/SmsVdp.h"
#include "SMS/SmsPsg.h"
#include "SMS/SmsFmAudio.h"
#include "SMS/SmsMemoryManager.h"
#include "SMS/SmsDefaultVideoFilter.h"
#include "SMS/SmsNtscFilter.h"
#include "SMS/SmsTypes.h"
#include "SMS/Carts/SmsSegaCart.h"
#include "SMS/Carts/SmsCodemasterCart.h"
#include "SMS/Carts/SmsMsxCart.h"
#include "SMS/Carts/SmsKoreanCart.h"
#include "SMS/Carts/SmsNemesisCart.h"
#include "Shared/Emulator.h"
#include "Shared/CheatManager.h"
#include "Shared/FirmwareHelper.h"
#include "Utilities/Serializer.h"
#include "Utilities/StringUtilities.h"
#include "Utilities/CRC32.h"
SmsConsole::SmsConsole(Emulator* emu)
{
_emu = emu;
}
SmsConsole::~SmsConsole()
{
}
LoadRomResult SmsConsole::LoadRom(VirtualFile& romFile)
{
vector<uint8_t> romData;
romFile.ReadFile(romData);
if(romData.size() >= 0x100) {
if((romData.size() % 0x400) == 0x200) {
//ROM has 512-byte copier header, ignore it
romData.erase(romData.begin(), romData.begin() + 0x200);
}
_filename = romFile.GetFileName();
string ext = romFile.GetFileExtension();
bool isGameGear = ext == ".gg";
if(isGameGear) {
_romFormat = RomFormat::GameGear;
_model = SmsModel::GameGear;
} else if(ext == ".col") {
_romFormat = RomFormat::ColecoVision;
_model = SmsModel::ColecoVision;
} else if(ext == ".sg") {
_romFormat = RomFormat::Sg;
_model = SmsModel::Sg;
} else {
_romFormat = RomFormat::Sms;
_model = SmsModel::Sms;
}
_vdp.reset(new SmsVdp());
_memoryManager.reset(new SmsMemoryManager());
_cpu.reset(new SmsCpu());
_psg.reset(new SmsPsg(_emu, this));
_fmAudio.reset(new SmsFmAudio(_emu, this));
_controlManager.reset(new SmsControlManager(_emu, this, _vdp.get()));
InitCart(romData);
vector<uint8_t> biosRom;
if(_model == SmsModel::ColecoVision) {
FirmwareHelper::LoadColecoVisionBios(_emu, biosRom);
} else {
FirmwareHelper::LoadSmsBios(_emu, biosRom, isGameGear);
}
_memoryManager->Init(_emu, this, romData, biosRom, _vdp.get(), _controlManager.get(), _cart.get(), _psg.get(), _fmAudio.get());
_vdp->Init(_emu, this, _cpu.get(), _controlManager.get(), _memoryManager.get());
_cpu->Init(_emu, this, _memoryManager.get());
UpdateRegion(true);
return LoadRomResult::Success;
}
return LoadRomResult::Failure;
}
void SmsConsole::InitCart(vector<uint8_t>& romData)
{
bool isCodemasterMapper = false;
if(romData.size() > 0x8000) {
//Codemaster games have a checksum+complement at 7FE6-7FE9
uint32_t checksum = romData[0x7FE6] | (romData[0x7FE7] << 8);
uint32_t complement = romData[0x7FE8] | (romData[0x7FE9] << 8);
isCodemasterMapper = (complement + checksum) == 0x10000;
}
if(isCodemasterMapper) {
_cart.reset(new SmsCodemasterCart(_memoryManager.get()));
} else {
uint32_t crc = CRC32::GetCRC(romData);
switch(crc) {
case 0x77EFE84A: //Cyborg Z
case 0x06965ED9: //F-1 Spirit - The way to Formula-1
case 0xF89AF3CC: //Knightmare II: The Maze of Galious
case 0x0A77FA5E: //Nemesis 2
case 0x445525E2: //Penguin Adventure
case 0x83F0EEDE: //Street Master
case 0x9195C34C: //Super Boy 3
case 0xA05258F5: //Wonsiin
_cart.reset(new SmsMsxCart(_memoryManager.get()));
break;
case 0xE316C06D: //Nemesis
_cart.reset(new SmsNemesisCart(_memoryManager.get(), (uint32_t)romData.size()));
break;
case 0x89B79E77: //Dallyeora Pigu-Wang
case 0x97D03541: //Samgukji 3
case 0x929222C4: //Jang Pung II
case 0x18FB98A3: //Jang Pung 3
_cart.reset(new SmsKoreanCart(_memoryManager.get()));
break;
//TODOSMS
//case 0x192949D5: //Janggun-ui Adeul
default:
if(romData.size() <= 0xC000) {
//No mapper (up to 48kb of prg rom)
_cart.reset(new SmsCart(_memoryManager.get()));
} else {
_cart.reset(new SmsSegaCart(_memoryManager.get()));
}
break;
}
}
}
void SmsConsole::Reset()
{
//SMS/GG has no physical reset button, behave like power cycle
_emu->ReloadRom(true);
}
void SmsConsole::RunFrame()
{
UpdateRegion(false);
uint32_t frame = _vdp->GetFrameCount();
while(frame == _vdp->GetFrameCount()) {
_cpu->Exec();
}
_psg->Run();
_psg->PlayQueuedAudio();
}
void SmsConsole::ProcessEndOfFrame()
{
_controlManager->UpdateControlDevices();
_controlManager->UpdateInputState();
}
void SmsConsole::UpdateRegion(bool forceUpdate)
{
ConsoleRegion region;
switch(_model) {
default:
case SmsModel::Sms: region = _emu->GetSettings()->GetSmsConfig().Region; break;
case SmsModel::GameGear: region = _emu->GetSettings()->GetSmsConfig().GameGearRegion; break;
case SmsModel::ColecoVision: region = _emu->GetSettings()->GetCvConfig().Region; break;
}
if(region == ConsoleRegion::Auto) {
string filename = StringUtilities::ToLower(_filename);
if(filename.find("(europe)") != string::npos || filename.find("(e)") != string::npos) {
region = ConsoleRegion::Pal;
} else {
if(_model == SmsModel::GameGear && (filename.find("(japan)") != string::npos || filename.find("(j)") != string::npos)) {
region = ConsoleRegion::NtscJapan;
} else {
region = ConsoleRegion::Ntsc;
}
}
}
if(_region != region || forceUpdate) {
_region = region;
_vdp->SetRegion(_model == SmsModel::GameGear ? ConsoleRegion::Ntsc : _region);
_psg->SetRegion(_model == SmsModel::GameGear ? ConsoleRegion::Ntsc : _region);
}
}
void SmsConsole::SaveBattery()
{
_memoryManager->SaveBattery();
}
bool SmsConsole::IsPsgAudioMuted()
{
return _fmAudio->IsPsgAudioMuted();
}
bool SmsConsole::HasBios()
{
return _memoryManager->HasBios();
}
BaseControlManager* SmsConsole::GetControlManager()
{
return _controlManager.get();
}
SmsRevision SmsConsole::GetRevision()
{
if(_model == SmsModel::Sg || _model == SmsModel::ColecoVision) {
return SmsRevision::Sms1;
} else if(_model == SmsModel::GameGear) {
return SmsRevision::Sms2;
}
return _emu->GetSettings()->GetSmsConfig().Revision;
}
ConsoleRegion SmsConsole::GetRegion()
{
return _region;
}
ConsoleType SmsConsole::GetConsoleType()
{
return ConsoleType::Sms;
}
vector<CpuType> SmsConsole::GetCpuTypes()
{
return { CpuType::Sms };
}
uint64_t SmsConsole::GetMasterClock()
{
return _cpu->GetState().CycleCount;
}
uint32_t SmsConsole::GetMasterClockRate()
{
return (_model != SmsModel::GameGear && _region == ConsoleRegion::Pal) ? 3546895 : 3579545;
}
double SmsConsole::GetFps()
{
return (_model != SmsModel::GameGear && _region == ConsoleRegion::Pal) ? 49.701460 : 59.9227434;
}
BaseVideoFilter* SmsConsole::GetVideoFilter(bool getDefaultFilter)
{
if(getDefaultFilter) {
return new SmsDefaultVideoFilter(_emu, this);
}
VideoFilterType filterType = _emu->GetSettings()->GetVideoConfig().VideoFilter;
switch(filterType) {
case VideoFilterType::NtscBisqwit:
case VideoFilterType::NtscBlargg:
return new SmsNtscFilter(_emu, this);
default: return new SmsDefaultVideoFilter(_emu, this);
}
}
PpuFrameInfo SmsConsole::GetPpuFrame()
{
PpuFrameInfo frame = {};
frame.FirstScanline = 0;
frame.FrameCount = _vdp->GetFrameCount();
frame.Width = 256;
frame.Height = 240;
frame.ScanlineCount = _region == ConsoleRegion::Ntsc ? 262 : 313;
frame.CycleCount = 342;
frame.FrameBufferSize = 256*240*sizeof(uint16_t);
frame.FrameBuffer = (uint8_t*)_vdp->GetScreenBuffer(false);
return frame;
}
RomFormat SmsConsole::GetRomFormat()
{
return _romFormat;
}
AudioTrackInfo SmsConsole::GetAudioTrackInfo()
{
//TODOSMS audio player
return AudioTrackInfo();
}
void SmsConsole::ProcessAudioPlayerAction(AudioPlayerActionParams p)
{
//TODOSMS audio player
}
void SmsConsole::RefreshRamCheats()
{
//Used to refresh pro action replay codes when vertical blank IRQ is triggered
for(InternalCheatCode& code : _emu->GetCheatManager()->GetRamRefreshCheats(CpuType::Sms)) {
_memoryManager->DebugWrite(code.Address, code.Value);
}
}
AddressInfo SmsConsole::GetAbsoluteAddress(uint32_t relAddress)
{
return _memoryManager->GetAbsoluteAddress(relAddress);
}
AddressInfo SmsConsole::GetAbsoluteAddress(AddressInfo& relAddress)
{
return GetAbsoluteAddress(relAddress.Address);
}
AddressInfo SmsConsole::GetRelativeAddress(AddressInfo& absAddress, CpuType cpuType)
{
return { _memoryManager->GetRelativeAddress(absAddress), MemoryType::SmsMemory };
}
SmsState SmsConsole::GetState()
{
SmsState state;
state.Cpu = _cpu->GetState();
state.Vdp = _vdp->GetState();
state.Psg = _psg->GetState();
state.MemoryManager = _memoryManager->GetState();
state.ControlManager = _controlManager->GetState();
return state;
}
void SmsConsole::GetConsoleState(BaseState& state, ConsoleType consoleType)
{
(SmsState&)state = GetState();
}
void SmsConsole::InitializeRam(void* data, uint32_t length)
{
EmuSettings* settings = _emu->GetSettings();
RamState ramState = _model == SmsModel::ColecoVision ? settings->GetCvConfig().RamPowerOnState : settings->GetSmsConfig().RamPowerOnState;
settings->InitializeRam(ramState, data, length);
}
void SmsConsole::Serialize(Serializer& s)
{
SV(_cpu);
SV(_vdp);
SV(_controlManager);
SV(_cart); //Process cart before memory manager to ensure mappings are updated properly
SV(_memoryManager);
SV(_psg);
SV(_fmAudio);
}