mirror of
https://github.com/libretro/MesenCE.git
synced 2026-11-10 11:11:32 -10:00
PaletteLatch, SpriteOverflowIndex and OpenBus were missing which could potentially cause issues with rewind, movies, etc. Reordered the serialization calls to match the order of the members (and make it simpler to compare in the future)
563 lines
14 KiB
C++
563 lines
14 KiB
C++
#include "pch.h"
|
|
#include "SMS/SmsMemoryManager.h"
|
|
#include "SMS/SmsConsole.h"
|
|
#include "SMS/SmsVdp.h"
|
|
#include "SMS/SmsPsg.h"
|
|
#include "SMS/SmsFmAudio.h"
|
|
#include "SMS/SmsBiosMapper.h"
|
|
#include "SMS/Carts/SmsCart.h"
|
|
#include "SMS/SmsControlManager.h"
|
|
#include "Shared/Emulator.h"
|
|
#include "Shared/BatteryManager.h"
|
|
#include "Shared/EmuSettings.h"
|
|
#include "Utilities/CRC32.h"
|
|
|
|
SmsMemoryManager::SmsMemoryManager()
|
|
{
|
|
}
|
|
|
|
SmsMemoryManager::~SmsMemoryManager()
|
|
{
|
|
delete[] _workRam;
|
|
delete[] _cartRam;
|
|
delete[] _prgRom;
|
|
delete[] _originalCartRam;
|
|
}
|
|
|
|
void SmsMemoryManager::Init(Emulator* emu, SmsConsole* console, vector<uint8_t>& romData, vector<uint8_t>& biosRom, SmsVdp* vdp, SmsControlManager* controlManager, SmsCart* cart, SmsPsg* psg, SmsFmAudio* fmAudio)
|
|
{
|
|
_emu = emu;
|
|
_console = console;
|
|
_vdp = vdp;
|
|
_psg = psg;
|
|
_controlManager = controlManager;
|
|
_cart = cart;
|
|
_fmAudio = fmAudio;
|
|
|
|
_prgRom = new uint8_t[romData.size()];
|
|
_prgRomSize = (uint32_t)romData.size();
|
|
memcpy(_prgRom, romData.data(), _prgRomSize);
|
|
_emu->RegisterMemory(MemoryType::SmsPrgRom, _prgRom, _prgRomSize);
|
|
|
|
_model = _console->GetModel();
|
|
bool isSg1000 = _model == SmsModel::Sg;
|
|
bool isCv = _model == SmsModel::ColecoVision;
|
|
|
|
_workRamSize = isCv ? SmsMemoryManager::CvWorkRamSize : SmsMemoryManager::SmsWorkRamSize;
|
|
|
|
_workRam = new uint8_t[_workRamSize];
|
|
console->InitializeRam(_workRam, _workRamSize);
|
|
_emu->RegisterMemory(MemoryType::SmsWorkRam, _workRam, _workRamSize);
|
|
|
|
if(biosRom.size() >= 0x400) {
|
|
_biosRom = new uint8_t[biosRom.size()];
|
|
_biosRomSize = (uint32_t)biosRom.size();
|
|
memcpy(_biosRom, biosRom.data(), _biosRomSize);
|
|
_state.BiosEnabled = true;
|
|
_emu->RegisterMemory(MemoryType::SmsBootRom, _biosRom, _biosRomSize);
|
|
if(!isCv) {
|
|
_biosMapper.reset(new SmsBiosMapper(this));
|
|
}
|
|
} else {
|
|
if(isSg1000) {
|
|
_state.CardEnabled = true;
|
|
} else {
|
|
_state.CartridgeEnabled = true;
|
|
|
|
//Setup work ram to match SMS' post-bios state
|
|
memset(_workRam, 0, _workRamSize);
|
|
|
|
//default value for $3E that some games expect after bios runs
|
|
_workRam[0] = _model == SmsModel::GameGear ? 0xA8 : 0xAB;
|
|
}
|
|
}
|
|
|
|
if(_model == SmsModel::GameGear) {
|
|
_state.CartridgeEnabled = true;
|
|
}
|
|
|
|
if(isSg1000) {
|
|
_cartRamSize = DetectSgCartRam(romData);
|
|
} else if(isCv) {
|
|
_cartRamSize = 0;
|
|
} else {
|
|
_cartRamSize = SmsMemoryManager::CartRamMaxSize;
|
|
}
|
|
|
|
_cartRam = new uint8_t[_cartRamSize];
|
|
console->InitializeRam(_cartRam, _cartRamSize);
|
|
_emu->RegisterMemory(MemoryType::SmsCartRam, _cartRam, _cartRamSize);
|
|
|
|
if(!isSg1000 && !isCv) {
|
|
LoadBattery();
|
|
_originalCartRam = new uint8_t[_cartRamSize];
|
|
memcpy(_originalCartRam, _cartRam, _cartRamSize);
|
|
}
|
|
|
|
_state.WorkRamEnabled = true;
|
|
_state.IoEnabled = true;
|
|
|
|
//Power on values unverified, probably incorrect?
|
|
_state.GgExtConfig = 0x7F;
|
|
_state.GgExtData = 0xFF;
|
|
|
|
RefreshMappings();
|
|
}
|
|
|
|
void SmsMemoryManager::RefreshMappings()
|
|
{
|
|
Unmap(0, 0xFFFF);
|
|
MapRegisters(0, 0xFFFF, SmsRegisterAccess::None);
|
|
if(_model == SmsModel::ColecoVision) {
|
|
Map(0x0000, 0x1FFF, MemoryType::SmsBootRom, 0, true);
|
|
for(int i = 0; i < 8; i++) {
|
|
Map(0x6000 + i * 0x400, 0x63FF + i * 0x400, MemoryType::SmsWorkRam, 0, false);
|
|
}
|
|
|
|
//Don't mirror rom (Sammy Lightfoot breaks if mirrored)
|
|
Map(0x8000, std::min<int>(0x8000 + _prgRomSize - 1, 0xFFFF), MemoryType::SmsPrgRom, 0, true);
|
|
} else {
|
|
if(_state.CartridgeEnabled && _model != SmsModel::Sg) {
|
|
_cart->RefreshMappings();
|
|
}
|
|
|
|
if(_state.CardEnabled && _model == SmsModel::Sg) {
|
|
Map(0x0000, 0xBFFF, MemoryType::SmsPrgRom, 0, true);
|
|
if(_sgRamMapAddress >= 0 && _cartRamSize > 0) {
|
|
Map(_sgRamMapAddress, _sgRamMapAddress + _cartRamSize - 1, MemoryType::SmsCartRam, 0, false);
|
|
}
|
|
}
|
|
|
|
if(_state.WorkRamEnabled) {
|
|
Map(0xC000, 0xFFFF, MemoryType::SmsWorkRam, 0, false);
|
|
}
|
|
|
|
if(_biosMapper && _state.BiosEnabled && (_model == SmsModel::GameGear || !_state.CartridgeEnabled)) {
|
|
bool enableCart = _model == SmsModel::GameGear;
|
|
_biosMapper->RefreshMappings(enableCart);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool SmsMemoryManager::HasBios()
|
|
{
|
|
return _biosMapper != nullptr;
|
|
}
|
|
|
|
void SmsMemoryManager::LoadBattery()
|
|
{
|
|
_emu->GetBatteryManager()->LoadBattery(".sav", _cartRam, SmsMemoryManager::CartRamMaxSize);
|
|
}
|
|
|
|
void SmsMemoryManager::SaveBattery()
|
|
{
|
|
if(_originalCartRam == nullptr) {
|
|
//SG-1000 with ext ram adapter (Taiwanese games)
|
|
return;
|
|
}
|
|
|
|
if(memcmp(_cartRam, _originalCartRam, SmsMemoryManager::CartRamMaxSize) != 0) {
|
|
uint32_t batterySize = 0;
|
|
//Select size of the .sav based on which portions of the 32kb buffer were changed
|
|
if(memcmp(_cartRam + 0x4000, _originalCartRam + 0x4000, 0x4000) != 0) {
|
|
batterySize = 0x8000;
|
|
} else if(memcmp(_cartRam + 0x2000, _originalCartRam + 0x2000, 0x2000) != 0) {
|
|
batterySize = 0x4000;
|
|
} else if(memcmp(_cartRam, _originalCartRam, 0x2000) != 0) {
|
|
batterySize = 0x2000;
|
|
}
|
|
batterySize = std::max(batterySize, _emu->GetBatteryManager()->GetBatteryFileSize(".sav"));
|
|
if(batterySize > 0) {
|
|
_emu->GetBatteryManager()->SaveBattery(".sav", _cartRam, batterySize);
|
|
}
|
|
}
|
|
}
|
|
|
|
AddressInfo SmsMemoryManager::GetAbsoluteAddress(uint16_t addr)
|
|
{
|
|
AddressInfo addrInfo = { -1, MemoryType::None };
|
|
|
|
uint8_t* ptr = _reads[addr >> 8];
|
|
if(!ptr) {
|
|
return addrInfo;
|
|
}
|
|
|
|
ptr += (addr & 0xFF);
|
|
|
|
if(ptr >= _prgRom && ptr < _prgRom + _prgRomSize) {
|
|
addrInfo.Address = (int32_t)(ptr - _prgRom);
|
|
addrInfo.Type = MemoryType::SmsPrgRom;
|
|
} else if(ptr >= _workRam && ptr < _workRam + _workRamSize) {
|
|
addrInfo.Address = (int32_t)(ptr - _workRam);
|
|
addrInfo.Type = MemoryType::SmsWorkRam;
|
|
} else if(ptr >= _cartRam && ptr < _cartRam + SmsMemoryManager::CartRamMaxSize) {
|
|
addrInfo.Address = (int32_t)(ptr - _cartRam);
|
|
addrInfo.Type = MemoryType::SmsCartRam;
|
|
} else if(_biosRom && ptr >= _biosRom && ptr < _biosRom + _biosRomSize) {
|
|
addrInfo.Address = (int32_t)(ptr - _biosRom);
|
|
addrInfo.Type = MemoryType::SmsBootRom;
|
|
}
|
|
return addrInfo;
|
|
}
|
|
|
|
int32_t SmsMemoryManager::GetRelativeAddress(AddressInfo& absAddress)
|
|
{
|
|
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;
|
|
}
|
|
|
|
void SmsMemoryManager::Map(uint16_t start, uint16_t end, MemoryType type, uint32_t offset, bool readonly)
|
|
{
|
|
uint8_t* src = (uint8_t*)_emu->GetMemory(type).Memory;
|
|
uint32_t size = _emu->GetMemory(type).Size;
|
|
|
|
if(size > 0) {
|
|
while(offset >= size) {
|
|
offset -= size;
|
|
}
|
|
|
|
src += offset;
|
|
for(int i = start; i < end; i += 0x100) {
|
|
_reads[i >> 8] = src;
|
|
_writes[i >> 8] = readonly ? nullptr : src;
|
|
|
|
if(src) {
|
|
src += 0x100;
|
|
offset = (offset + 0x100);
|
|
if(offset >= size) {
|
|
offset = 0;
|
|
src = (uint8_t*)_emu->GetMemory(type).Memory;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
Unmap(start, end);
|
|
}
|
|
}
|
|
|
|
void SmsMemoryManager::Unmap(uint16_t start, uint16_t end)
|
|
{
|
|
for(int i = start; i < end; i += 0x100) {
|
|
_reads[i >> 8] = nullptr;
|
|
_writes[i >> 8] = nullptr;
|
|
}
|
|
}
|
|
|
|
void SmsMemoryManager::MapRegisters(uint16_t start, uint16_t end, SmsRegisterAccess access)
|
|
{
|
|
for(int i = start; i <= end; i += 0x100) {
|
|
_state.IsReadRegister[i >> 8] = ((int)access & (int)SmsRegisterAccess::Read) != 0;
|
|
_state.IsWriteRegister[i >> 8] = ((int)access & (int)SmsRegisterAccess::Write) != 0;
|
|
}
|
|
}
|
|
|
|
uint8_t SmsMemoryManager::GetOpenBus()
|
|
{
|
|
return _console->GetRevision() == SmsRevision::Sms1 ? _state.OpenBus : 0xFF;
|
|
}
|
|
|
|
uint8_t SmsMemoryManager::DebugRead(uint16_t addr)
|
|
{
|
|
if(_state.IsReadRegister[addr >> 8]) {
|
|
return _cart->PeekRegister(addr);
|
|
}
|
|
|
|
uint8_t* data = _reads[addr >> 8];
|
|
return data ? data[(uint8_t)addr] : GetOpenBus();
|
|
}
|
|
|
|
void SmsMemoryManager::Write(uint16_t addr, uint8_t value)
|
|
{
|
|
if(_emu->ProcessMemoryWrite<CpuType::Sms>(addr, value, MemoryOperationType::Write)) {
|
|
if(_state.IsWriteRegister[addr >> 8]) {
|
|
if(_state.CartridgeEnabled) {
|
|
_cart->WriteRegister(addr, value);
|
|
}
|
|
if(_biosMapper && _state.BiosEnabled) {
|
|
_biosMapper->WriteRegister(addr, value);
|
|
}
|
|
}
|
|
if(_writes[addr >> 8]) {
|
|
_writes[addr >> 8][(uint8_t)addr] = value;
|
|
}
|
|
_state.OpenBus = value;
|
|
}
|
|
}
|
|
|
|
void SmsMemoryManager::DebugWrite(uint16_t addr, uint8_t value)
|
|
{
|
|
//TODOSMS - allow side-effects for debugger
|
|
uint8_t* data = _writes[addr >> 8];
|
|
if(data) {
|
|
data[(uint8_t)addr] = value;
|
|
}
|
|
}
|
|
|
|
uint8_t SmsMemoryManager::DebugReadPort(uint8_t port)
|
|
{
|
|
return InternalReadPort<true>(port);
|
|
}
|
|
|
|
uint8_t SmsMemoryManager::ReadPort(uint8_t port)
|
|
{
|
|
return InternalReadPort<false>(port);
|
|
}
|
|
|
|
template<bool isPeek>
|
|
uint8_t SmsMemoryManager::InternalReadPort(uint8_t port)
|
|
{
|
|
uint8_t value;
|
|
switch(_model) {
|
|
case SmsModel::ColecoVision: value = ReadColecoVisionPort<isPeek>(port); break;
|
|
case SmsModel::GameGear: value = ReadGameGearPort<isPeek>(port); break;
|
|
default: value = ReadSmsPort<isPeek>(port);
|
|
}
|
|
|
|
if constexpr(!isPeek) {
|
|
_emu->ProcessMemoryAccess<CpuType::Sms, MemoryType::SmsPort, MemoryOperationType::Read>(port, value);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
void SmsMemoryManager::WritePort(uint8_t port, uint8_t value)
|
|
{
|
|
_emu->ProcessMemoryAccess<CpuType::Sms, MemoryType::SmsPort, MemoryOperationType::Write>(port, value);
|
|
switch(_model) {
|
|
case SmsModel::ColecoVision: WriteColecoVisionPort(port, value); break;
|
|
case SmsModel::GameGear: WriteGameGearPort(port, value); break;
|
|
default: WriteSmsPort(port, value); break;
|
|
}
|
|
}
|
|
|
|
void SmsMemoryManager::WriteSmsPort(uint8_t port, uint8_t value)
|
|
{
|
|
bool isGameGear = _model == SmsModel::GameGear;
|
|
|
|
switch(port & 0xC1) {
|
|
case 0x00: {
|
|
//Port 3E (Memory Control)
|
|
bool isSms1 = _console->GetRevision() == SmsRevision::Sms1;
|
|
_state.ExpEnabled = (value & 0x80) == 0 && !isSms1; //TODOSMS not implemented
|
|
_state.CartridgeEnabled = (value & 0x40) == 0 || isGameGear;
|
|
_state.CardEnabled = (value & 0x20) == 0 && !isSms1;
|
|
_state.WorkRamEnabled = (value & 0x10) == 0;
|
|
_state.BiosEnabled = (value & 0x08) == 0;
|
|
_state.IoEnabled = (value & 0x04) == 0 || isGameGear;
|
|
RefreshMappings();
|
|
break;
|
|
}
|
|
|
|
case 0x01:
|
|
//Port 3F (IO Port Control)
|
|
_controlManager->WriteControlPort(value);
|
|
break;
|
|
|
|
case 0x40:
|
|
case 0x41:
|
|
_psg->Write(value);
|
|
break;
|
|
|
|
case 0x80:
|
|
case 0x81:
|
|
//Ports: BE (VDP data), BF (VDP control)
|
|
_vdp->WritePort(port, value);
|
|
break;
|
|
|
|
case 0xC0:
|
|
case 0xC1:
|
|
if(_emu->GetSettings()->GetSmsConfig().EnableFmAudio) {
|
|
_fmAudio->Write(port, value);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void SmsMemoryManager::WriteColecoVisionPort(uint8_t port, uint8_t value)
|
|
{
|
|
switch(port & 0xE0) {
|
|
case 0x80: _controlManager->WriteControlPort(0); break;
|
|
case 0xC0: _controlManager->WriteControlPort(1); break;
|
|
case 0xE0: _psg->Write(value); break;
|
|
case 0xA0: _vdp->WritePort(port, value); break;
|
|
}
|
|
}
|
|
|
|
void SmsMemoryManager::WriteGameGearPort(uint8_t port, uint8_t value)
|
|
{
|
|
switch(port) {
|
|
case 0:
|
|
//read-only
|
|
break;
|
|
|
|
//TODOSMS GG - input/output ext port
|
|
case 1: _state.GgExtData = value & 0x7F; break;
|
|
case 2: _state.GgExtConfig = value; break;
|
|
case 3: _state.GgSendData = value; break;
|
|
case 4: break; //read-only
|
|
case 5: _state.GgSerialConfig = value; break;
|
|
|
|
//Sound panning - write-only
|
|
case 6:
|
|
_psg->WritePanningReg(value);
|
|
break;
|
|
|
|
default:
|
|
WriteSmsPort(port, value);
|
|
break;
|
|
}
|
|
}
|
|
|
|
template<bool isPeek>
|
|
uint8_t SmsMemoryManager::ReadSmsPort(uint8_t port)
|
|
{
|
|
switch(port & 0xC1) {
|
|
case 0x00: return GetOpenBus(); //Port 3E (Memory Control)
|
|
|
|
case 0x40:
|
|
case 0x41:
|
|
case 0x80:
|
|
case 0x81:
|
|
//Ports: 7E (V counter), 7F (H counter), BE (VDP data), BF (VDP control)
|
|
return isPeek ? _vdp->PeekPort(port) : _vdp->ReadPort(port);
|
|
|
|
case 0xC0:
|
|
//Port DC (IO Port A/B)
|
|
if(_state.IoEnabled) {
|
|
return _controlManager->ReadPort(0);
|
|
} else if(port == 0xF2) {
|
|
if(_emu->GetSettings()->GetSmsConfig().EnableFmAudio) {
|
|
return _fmAudio->Read();
|
|
} else {
|
|
return GetOpenBus();
|
|
}
|
|
}
|
|
return GetOpenBus();
|
|
|
|
case 0xC1: return _state.IoEnabled ? _controlManager->ReadPort(1) : GetOpenBus(); //Port DD (IO Port B/Misc)
|
|
|
|
default: return GetOpenBus();
|
|
}
|
|
}
|
|
|
|
template<bool isPeek>
|
|
uint8_t SmsMemoryManager::ReadColecoVisionPort(uint8_t port)
|
|
{
|
|
switch(port & 0xE0) {
|
|
case 0xA0: return isPeek ? _vdp->PeekPort(port) : _vdp->ReadPort(port);
|
|
case 0xE0: return _controlManager->ReadPort((port >> 1) & 0x01);
|
|
default: return 0xFF;
|
|
}
|
|
}
|
|
|
|
template<bool isPeek>
|
|
uint8_t SmsMemoryManager::ReadGameGearPort(uint8_t port)
|
|
{
|
|
switch(port) {
|
|
case 0: {
|
|
//start button, region, pal/ntsc
|
|
ConsoleRegion region = _console->GetRegion();
|
|
return (
|
|
(_controlManager->IsPausePressed() ? 0x00 : 0x80) |
|
|
(region == ConsoleRegion::NtscJapan ? 0x00 : 0x40) |
|
|
(region == ConsoleRegion::Pal ? 0x20 : 0x00));
|
|
}
|
|
|
|
//TODOSMS GG - input/output ext port
|
|
case 1: return _state.GgExtData;
|
|
case 2: return _state.GgExtConfig;
|
|
case 3: return _state.GgSendData;
|
|
case 4: return 0xFF;
|
|
case 5: return _state.GgSerialConfig;
|
|
|
|
//Sound panning - write-only
|
|
case 6: return 0xFF;
|
|
|
|
default:
|
|
if(port < 0x40) {
|
|
return 0xFF;
|
|
} else if(port < 0xC0) {
|
|
return isPeek ? _vdp->PeekPort(port) : _vdp->ReadPort(port);
|
|
} else {
|
|
switch(port) {
|
|
case 0xC0:
|
|
case 0xDC:
|
|
return _controlManager->ReadPort(0);
|
|
|
|
case 0xC1:
|
|
case 0xDD:
|
|
return _controlManager->ReadPort(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0xFF;
|
|
}
|
|
|
|
uint32_t SmsMemoryManager::DetectSgCartRam(vector<uint8_t>& romData)
|
|
{
|
|
//Used by Taiwanese games that require 8kb of ram at 2000-3FFF
|
|
//These roms have 2000-3FFF filled with FF, this is used to detect them
|
|
bool isUnlicensedRom = true;
|
|
if(romData.size() >= 0x4000) {
|
|
for(int i = 0; i < 0x2000; i++) {
|
|
if(romData[0x2000 + i] != 0xFF) {
|
|
isUnlicensedRom = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(isUnlicensedRom) {
|
|
_sgRamMapAddress = 0x2000;
|
|
return 0x2000;
|
|
} else {
|
|
//SG-1000 games that had extra ram in cart
|
|
uint32_t crc = CRC32::GetCRC(romData);
|
|
if(crc == 0x092F29D6) {
|
|
//The Castle, 8kb at $8000
|
|
_sgRamMapAddress = 0x8000;
|
|
return 0x2000;
|
|
} else if(crc == 0xAF4F14BC) {
|
|
//Othello, 2kb at $8000
|
|
_sgRamMapAddress = 0x8000;
|
|
return 0x800;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void SmsMemoryManager::Serialize(Serializer& s)
|
|
{
|
|
SV(_masterClock);
|
|
if(_biosMapper) {
|
|
SV(_biosMapper);
|
|
}
|
|
|
|
SV(_state.ExpEnabled);
|
|
SV(_state.CartridgeEnabled);
|
|
SV(_state.CardEnabled);
|
|
SV(_state.WorkRamEnabled);
|
|
SV(_state.BiosEnabled);
|
|
SV(_state.IoEnabled);
|
|
|
|
SV(_state.OpenBus);
|
|
SV(_state.GgExtData);
|
|
SV(_state.GgExtConfig);
|
|
SV(_state.GgSendData);
|
|
SV(_state.GgSerialConfig);
|
|
|
|
if(_cartRamSize > 0) {
|
|
SVArray(_cartRam, _cartRamSize);
|
|
}
|
|
SVArray(_workRam, _workRamSize);
|
|
|
|
if(!s.IsSaving()) {
|
|
RefreshMappings();
|
|
}
|
|
}
|