Core: Refactored nametable RAM management to fix some issues and remove some limitations
(This breaks save state compatibility)
This commit is contained in:
parent
97fb853d66
commit
ce68ce57c0
16 changed files with 151 additions and 295 deletions
|
@ -149,7 +149,7 @@ void BaseMapper::SetCpuMemoryMapping(uint16_t startAddr, uint16_t endAddr, uint8
|
|||
endAddr >>= 8;
|
||||
for(uint16_t i = startAddr; i <= endAddr; i++) {
|
||||
_prgPages[i] = source;
|
||||
_prgPageAccessType[i] = accessType != -1 ? accessType : (uint8_t)MemoryAccessType::Read;
|
||||
_prgMemoryAccess[i] = accessType != -1 ? (MemoryAccessType)accessType : MemoryAccessType::Read;
|
||||
|
||||
source += 0x100;
|
||||
}
|
||||
|
@ -215,6 +215,12 @@ void BaseMapper::SetPpuMemoryMapping(uint16_t startAddr, uint16_t endAddr, uint1
|
|||
pageCount = _chrRamSize / pageSize;
|
||||
defaultAccessType |= MemoryAccessType::Write;
|
||||
break;
|
||||
|
||||
case ChrMemoryType::NametableRam:
|
||||
pageSize = BaseMapper::NametableSize;
|
||||
pageCount = BaseMapper::NametableCount;
|
||||
defaultAccessType |= MemoryAccessType::Write;
|
||||
break;
|
||||
}
|
||||
|
||||
if(pageCount == 0) {
|
||||
|
@ -250,6 +256,7 @@ void BaseMapper::SetPpuMemoryMapping(uint16_t startAddr, uint16_t endAddr, ChrMe
|
|||
case ChrMemoryType::Default: sourceMemory = _onlyChrRam ? _chrRam : _chrRom; break;
|
||||
case ChrMemoryType::ChrRom: sourceMemory = _chrRom; break;
|
||||
case ChrMemoryType::ChrRam: sourceMemory = _chrRam; break;
|
||||
case ChrMemoryType::NametableRam: sourceMemory = _nametableRam; break;
|
||||
}
|
||||
int firstSlot = startAddr >> 8;
|
||||
int slotCount = (endAddr - startAddr + 1) >> 8;
|
||||
|
@ -272,7 +279,7 @@ void BaseMapper::SetPpuMemoryMapping(uint16_t startAddr, uint16_t endAddr, uint8
|
|||
endAddr >>= 8;
|
||||
for(uint16_t i = startAddr; i <= endAddr; i++) {
|
||||
_chrPages[i] = sourceMemory;
|
||||
_chrPageAccessType[i] = accessType != -1 ? accessType : (uint8_t)MemoryAccessType::ReadWrite;
|
||||
_chrMemoryAccess[i] = accessType != -1 ? (MemoryAccessType)accessType : MemoryAccessType::ReadWrite;
|
||||
|
||||
if(sourceMemory != nullptr) {
|
||||
sourceMemory += 0x100;
|
||||
|
@ -353,17 +360,17 @@ void BaseMapper::SelectChrPage2x(uint16_t slot, uint16_t page, ChrMemoryType mem
|
|||
|
||||
void BaseMapper::SelectCHRPage(uint16_t slot, uint16_t page, ChrMemoryType memoryType)
|
||||
{
|
||||
uint16_t pageSize = memoryType == ChrMemoryType::ChrRam ? InternalGetChrRamPageSize() : InternalGetChrPageSize();
|
||||
uint16_t pageSize;
|
||||
if(memoryType == ChrMemoryType::NametableRam) {
|
||||
pageSize = BaseMapper::NametableSize;
|
||||
} else {
|
||||
pageSize = memoryType == ChrMemoryType::ChrRam ? InternalGetChrRamPageSize() : InternalGetChrPageSize();
|
||||
}
|
||||
|
||||
uint16_t startAddr = slot * pageSize;
|
||||
uint16_t endAddr = startAddr + pageSize - 1;
|
||||
if(page == ChrSpecialPage::NametableA) {
|
||||
SetPpuMemoryMapping(startAddr, endAddr, GetNametable(0));
|
||||
} else if(page == ChrSpecialPage::NametableB) {
|
||||
SetPpuMemoryMapping(startAddr, endAddr, GetNametable(1));
|
||||
} else {
|
||||
SetPpuMemoryMapping(startAddr, endAddr, page, memoryType);
|
||||
}
|
||||
}
|
||||
|
||||
void BaseMapper::InitializeRam(void* data, uint32_t length)
|
||||
{
|
||||
|
@ -499,21 +506,14 @@ void BaseMapper::RemoveRegisterRange(uint16_t startAddr, uint16_t endAddr, Memor
|
|||
|
||||
void BaseMapper::StreamState(bool saving)
|
||||
{
|
||||
//Need to get the number of nametables in the state first, before we try to stream the nametable ram array
|
||||
Stream(_nametableCount);
|
||||
|
||||
ArrayInfo<uint8_t> chrRam = { _chrRam, _chrRamSize };
|
||||
ArrayInfo<uint8_t> workRam = { _workRam, _workRamSize };
|
||||
ArrayInfo<uint8_t> saveRam = { _saveRam, _saveRamSize };
|
||||
ArrayInfo<uint8_t> nametableRam = { _nametableRam, _nametableCount * BaseMapper::NametableSize };
|
||||
|
||||
uint32_t prgPages[64];
|
||||
uint32_t chrPages[64];
|
||||
|
||||
ArrayInfo<uint8_t> nametableIndexes = { _nametableIndexes, 4 };
|
||||
|
||||
if(GetStateVersion() < 9) {
|
||||
ArrayInfo<uint32_t> prgPageNumbers = { prgPages, 64 };
|
||||
ArrayInfo<uint32_t> chrPageNumbers = { chrPages, 64 };
|
||||
|
||||
Stream(_mirroringType, chrRam, workRam, saveRam, prgPageNumbers, chrPageNumbers, nametableIndexes);
|
||||
} else {
|
||||
ArrayInfo<int32_t> prgMemoryOffset = { _prgMemoryOffset, 0x100 };
|
||||
ArrayInfo<int32_t> chrMemoryOffset = { _chrMemoryOffset, 0x40 };
|
||||
ArrayInfo<PrgMemoryType> prgMemoryType = { _prgMemoryType, 0x100 };
|
||||
|
@ -521,50 +521,15 @@ void BaseMapper::StreamState(bool saving)
|
|||
ArrayInfo<MemoryAccessType> prgMemoryAccess = { _prgMemoryAccess, 0x100 };
|
||||
ArrayInfo<MemoryAccessType> chrMemoryAccess = { _chrMemoryAccess, 0x40 };
|
||||
|
||||
Stream(_mirroringType, chrRam, workRam, saveRam, nametableIndexes, prgMemoryOffset, chrMemoryOffset, prgMemoryType, chrMemoryType, prgMemoryAccess, chrMemoryAccess);
|
||||
}
|
||||
|
||||
if(GetStateVersion() >= 7) {
|
||||
bool hasExtraNametable[2] = { _cartNametableRam[0] != nullptr, _cartNametableRam[1] != nullptr };
|
||||
Stream(hasExtraNametable[0], hasExtraNametable[1]);
|
||||
|
||||
for(int i = 0; i < 2; i++) {
|
||||
if(hasExtraNametable[i]) {
|
||||
if(!_cartNametableRam[i]) {
|
||||
_cartNametableRam[i] = new uint8_t[0x400];
|
||||
}
|
||||
|
||||
ArrayInfo<uint8_t> ram = { _cartNametableRam[i], 0x400 };
|
||||
Stream(ram);
|
||||
}
|
||||
}
|
||||
}
|
||||
Stream(_mirroringType, chrRam, workRam, saveRam, nametableRam, prgMemoryOffset, chrMemoryOffset, prgMemoryType, chrMemoryType, prgMemoryAccess, chrMemoryAccess);
|
||||
|
||||
if(!saving) {
|
||||
RestorePrgChrState(prgPages, chrPages);
|
||||
|
||||
for(int i = 0; i < 4; i++) {
|
||||
SetNametable(i, _nametableIndexes[i]);
|
||||
}
|
||||
RestorePrgChrState();
|
||||
}
|
||||
}
|
||||
|
||||
void BaseMapper::RestorePrgChrState(uint32_t* prgPages, uint32_t* chrPages)
|
||||
void BaseMapper::RestorePrgChrState()
|
||||
{
|
||||
if(GetStateVersion() < 9) {
|
||||
//Support for older save states
|
||||
for(uint16_t i = 0; i < 64; i++) {
|
||||
if(prgPages[i] != 0xEEEEEEEE) {
|
||||
BaseMapper::SelectPRGPage(i, (uint16_t)prgPages[i]);
|
||||
}
|
||||
}
|
||||
|
||||
for(uint16_t i = 0; i < 64; i++) {
|
||||
if(chrPages[i] != 0xEEEEEEEE) {
|
||||
BaseMapper::SelectCHRPage(i, (uint16_t)chrPages[i]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for(uint16_t i = 0; i < 0x100; i++) {
|
||||
uint16_t startAddr = i << 8;
|
||||
if(_prgMemoryAccess[i] != MemoryAccessType::NoAccess) {
|
||||
|
@ -583,7 +548,6 @@ void BaseMapper::RestorePrgChrState(uint32_t* prgPages, uint32_t* chrPages)
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BaseMapper::Initialize(RomData &romData)
|
||||
{
|
||||
|
@ -609,8 +573,6 @@ void BaseMapper::Initialize(RomData &romData)
|
|||
memset(_isWriteRegisterAddr, 0, sizeof(_isWriteRegisterAddr));
|
||||
AddRegisterRange(RegisterStartAddress(), RegisterEndAddress(), MemoryOperation::Any);
|
||||
|
||||
_mirroringType = romData.Info.Mirroring;
|
||||
|
||||
_prgSize = (uint32_t)romData.PrgRom.size();
|
||||
_chrRomSize = (uint32_t)romData.ChrRom.size();
|
||||
_originalPrgRom = romData.PrgRom;
|
||||
|
@ -637,24 +599,18 @@ void BaseMapper::Initialize(RomData &romData)
|
|||
InitializeRam(_saveRam, _saveRamSize);
|
||||
InitializeRam(_workRam, _workRamSize);
|
||||
|
||||
memset(_cartNametableRam, 0, sizeof(_cartNametableRam));
|
||||
memset(_nametableIndexes, 0, sizeof(_nametableIndexes));
|
||||
|
||||
for(int i = 0; i <= 0xFF; i++) {
|
||||
//Allow us to map a different page every 256 bytes
|
||||
_prgPages[i] = nullptr;
|
||||
_prgPageAccessType[i] = MemoryAccessType::NoAccess;
|
||||
_chrPages[i] = nullptr;
|
||||
_chrPageAccessType[i] = MemoryAccessType::NoAccess;
|
||||
}
|
||||
_nametableCount = 2;
|
||||
_nametableRam = new uint8_t[BaseMapper::NametableSize*BaseMapper::NametableCount];
|
||||
InitializeRam(_nametableRam, BaseMapper::NametableSize*BaseMapper::NametableCount);
|
||||
|
||||
for(int i = 0; i < 0x100; i++) {
|
||||
//Allow us to map a different page every 256 bytes
|
||||
_prgPages[i] = nullptr;
|
||||
_prgMemoryOffset[i] = -1;
|
||||
_prgMemoryType[i] = PrgMemoryType::PrgRom;
|
||||
_prgMemoryAccess[i] = MemoryAccessType::NoAccess;
|
||||
}
|
||||
|
||||
for(int i = 0; i < 0x40; i++) {
|
||||
_chrPages[i] = nullptr;
|
||||
_chrMemoryOffset[i] = -1;
|
||||
_chrMemoryType[i] = ChrMemoryType::Default;
|
||||
_chrMemoryAccess[i] = MemoryAccessType::NoAccess;
|
||||
|
@ -687,6 +643,8 @@ void BaseMapper::Initialize(RomData &romData)
|
|||
|
||||
SetupDefaultWorkRam();
|
||||
|
||||
SetMirroringType(romData.Info.Mirroring);
|
||||
|
||||
InitMapper();
|
||||
InitMapper(romData);
|
||||
|
||||
|
@ -702,14 +660,7 @@ BaseMapper::~BaseMapper()
|
|||
delete[] _prgRom;
|
||||
delete[] _saveRam;
|
||||
delete[] _workRam;
|
||||
|
||||
if(_cartNametableRam[0]) {
|
||||
delete[] _cartNametableRam[0];
|
||||
}
|
||||
|
||||
if(_cartNametableRam[1]) {
|
||||
delete[] _cartNametableRam[1];
|
||||
}
|
||||
delete[] _nametableRam;
|
||||
}
|
||||
|
||||
void BaseMapper::ProcessNotification(ConsoleNotificationType type, void* parameter)
|
||||
|
@ -746,47 +697,35 @@ void BaseMapper::SetConsole(shared_ptr<Console> console)
|
|||
_console = console;
|
||||
}
|
||||
|
||||
void BaseMapper::SetDefaultNametables(uint8_t* nametableA, uint8_t* nametableB)
|
||||
uint8_t* BaseMapper::GetNametable(uint8_t nametableIndex)
|
||||
{
|
||||
_nesNametableRam[0] = nametableA;
|
||||
_nesNametableRam[1] = nametableB;
|
||||
SetMirroringType(_mirroringType);
|
||||
if(nametableIndex >= BaseMapper::NametableCount) {
|
||||
#ifdef _DEBUG
|
||||
MessageManager::Log("Invalid nametable index");
|
||||
#endif
|
||||
return _nametableRam;
|
||||
}
|
||||
_nametableCount = std::max<uint8_t>(_nametableCount, nametableIndex);
|
||||
|
||||
void BaseMapper::AddNametable(uint8_t index, uint8_t *nametable)
|
||||
{
|
||||
assert(index >= 4);
|
||||
_cartNametableRam[index - 2] = nametable;
|
||||
}
|
||||
|
||||
uint8_t* BaseMapper::GetNametable(uint8_t index)
|
||||
{
|
||||
if(index <= 1) {
|
||||
return _nesNametableRam[index];
|
||||
} else {
|
||||
return _cartNametableRam[index - 2];
|
||||
}
|
||||
return _nametableRam + (nametableIndex * BaseMapper::NametableSize);
|
||||
}
|
||||
|
||||
void BaseMapper::SetNametable(uint8_t index, uint8_t nametableIndex)
|
||||
{
|
||||
if(nametableIndex == 2 && _cartNametableRam[0] == nullptr) {
|
||||
_cartNametableRam[0] = new uint8_t[0x400];
|
||||
InitializeRam(_cartNametableRam[0], 0x400);
|
||||
}
|
||||
if(nametableIndex == 3 && _cartNametableRam[1] == nullptr) {
|
||||
_cartNametableRam[1] = new uint8_t[0x400];
|
||||
InitializeRam(_cartNametableRam[1], 0x400);
|
||||
if(nametableIndex >= BaseMapper::NametableCount) {
|
||||
#ifdef _DEBUG
|
||||
MessageManager::Log("Invalid nametable index");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
_nametableCount = std::max<uint8_t>(_nametableCount, nametableIndex);
|
||||
|
||||
_nametableIndexes[index] = nametableIndex;
|
||||
|
||||
SetPpuMemoryMapping(0x2000 + index * 0x400, 0x2000 + (index + 1) * 0x400 - 1, GetNametable(nametableIndex));
|
||||
SetPpuMemoryMapping(0x2000 + index * 0x400, 0x2000 + (index + 1) * 0x400 - 1, nametableIndex, ChrMemoryType::NametableRam);
|
||||
|
||||
//Mirror $2000-$2FFF to $3000-$3FFF, while keeping a distinction between the addresses
|
||||
//Previously, $3000-$3FFF was being "redirected" to $2000-$2FFF to avoid MMC3 IRQ issues (which is incorrect)
|
||||
//More info here: https://forums.nesdev.com/viewtopic.php?p=132145#p132145
|
||||
SetPpuMemoryMapping(0x3000 + index * 0x400, 0x3000 + (index + 1) * 0x400 - 1, GetNametable(nametableIndex));
|
||||
SetPpuMemoryMapping(0x3000 + index * 0x400, 0x3000 + (index + 1) * 0x400 - 1, nametableIndex, ChrMemoryType::NametableRam);
|
||||
}
|
||||
|
||||
void BaseMapper::SetNametables(uint8_t nametable1Index, uint8_t nametable2Index, uint8_t nametable3Index, uint8_t nametable4Index)
|
||||
|
@ -833,7 +772,7 @@ uint8_t BaseMapper::ReadRAM(uint16_t addr)
|
|||
{
|
||||
if(_allowRegisterRead && _isReadRegisterAddr[addr]) {
|
||||
return ReadRegister(addr);
|
||||
} else if(_prgPageAccessType[addr >> 8] & MemoryAccessType::Read) {
|
||||
} else if(_prgMemoryAccess[addr >> 8] & MemoryAccessType::Read) {
|
||||
return _prgPages[addr >> 8][(uint8_t)addr];
|
||||
} else {
|
||||
//assert(false);
|
||||
|
@ -848,7 +787,7 @@ uint8_t BaseMapper::PeekRAM(uint16_t addr)
|
|||
|
||||
uint8_t BaseMapper::DebugReadRAM(uint16_t addr)
|
||||
{
|
||||
if(_prgPageAccessType[addr >> 8] & MemoryAccessType::Read) {
|
||||
if(_prgMemoryAccess[addr >> 8] & MemoryAccessType::Read) {
|
||||
return _prgPages[addr >> 8][(uint8_t)addr];
|
||||
} else {
|
||||
//assert(false);
|
||||
|
@ -883,7 +822,7 @@ void BaseMapper::DebugWriteRAM(uint16_t addr, uint8_t value)
|
|||
|
||||
void BaseMapper::WritePrgRam(uint16_t addr, uint8_t value)
|
||||
{
|
||||
if(_prgPageAccessType[addr >> 8] & MemoryAccessType::Write) {
|
||||
if(_prgMemoryAccess[addr >> 8] & MemoryAccessType::Write) {
|
||||
_prgPages[addr >> 8][(uint8_t)addr] = value;
|
||||
}
|
||||
}
|
||||
|
@ -896,7 +835,7 @@ void BaseMapper::NotifyVRAMAddressChange(uint16_t addr)
|
|||
|
||||
uint8_t BaseMapper::InternalReadVRAM(uint16_t addr)
|
||||
{
|
||||
if(_chrPageAccessType[addr >> 8] & MemoryAccessType::Read) {
|
||||
if(_chrMemoryAccess[addr >> 8] & MemoryAccessType::Read) {
|
||||
return _chrPages[addr >> 8][(uint8_t)addr];
|
||||
}
|
||||
|
||||
|
@ -928,7 +867,7 @@ void BaseMapper::DebugWriteVRAM(uint16_t addr, uint8_t value, bool disableSideEf
|
|||
}
|
||||
} else {
|
||||
NotifyVRAMAddressChange(addr);
|
||||
if(_chrPageAccessType[addr >> 8] & MemoryAccessType::Write) {
|
||||
if(_chrMemoryAccess[addr >> 8] & MemoryAccessType::Write) {
|
||||
_chrPages[addr >> 8][(uint8_t)addr] = value;
|
||||
}
|
||||
}
|
||||
|
@ -938,7 +877,7 @@ void BaseMapper::WriteVRAM(uint16_t addr, uint8_t value)
|
|||
{
|
||||
_console->DebugProcessVramWriteOperation(addr, value);
|
||||
|
||||
if(_chrPageAccessType[addr >> 8] & MemoryAccessType::Write) {
|
||||
if(_chrMemoryAccess[addr >> 8] & MemoryAccessType::Write) {
|
||||
_chrPages[addr >> 8][(uint8_t)addr] = value;
|
||||
}
|
||||
}
|
||||
|
@ -1205,10 +1144,6 @@ CartridgeState BaseMapper::GetState()
|
|||
state.ChrMemoryAccess[i] = _chrMemoryAccess[i];
|
||||
}
|
||||
|
||||
for(int i = 0; i < 4; i++) {
|
||||
state.Nametables[i] = _nametableIndexes[i];
|
||||
}
|
||||
|
||||
state.WorkRamPageSize = GetWorkRamPageSize();
|
||||
state.SaveRamPageSize = GetSaveRamPageSize();
|
||||
|
||||
|
|
|
@ -25,9 +25,8 @@ private:
|
|||
uint16_t InternalGetChrRamPageSize();
|
||||
bool ValidateAddressRange(uint16_t startAddr, uint16_t endAddr);
|
||||
|
||||
uint8_t *_nesNametableRam[2];
|
||||
uint8_t *_cartNametableRam[10];
|
||||
uint8_t _nametableIndexes[4];
|
||||
uint8_t *_nametableRam = nullptr;
|
||||
uint8_t _nametableCount = 2;
|
||||
|
||||
bool _onlyChrRam = false;
|
||||
bool _hasBusConflicts = false;
|
||||
|
@ -36,23 +35,25 @@ private:
|
|||
bool _isReadRegisterAddr[0x10000];
|
||||
bool _isWriteRegisterAddr[0x10000];
|
||||
|
||||
MemoryAccessType _prgMemoryAccess[0x100];
|
||||
uint8_t* _prgPages[0x100];
|
||||
|
||||
MemoryAccessType _chrMemoryAccess[0x100];
|
||||
uint8_t* _chrPages[0x100];
|
||||
uint8_t _prgPageAccessType[0x100];
|
||||
uint8_t _chrPageAccessType[0x100];
|
||||
|
||||
int32_t _prgMemoryOffset[0x100];
|
||||
PrgMemoryType _prgMemoryType[0x100];
|
||||
MemoryAccessType _prgMemoryAccess[0x100];
|
||||
|
||||
int32_t _chrMemoryOffset[0x40];
|
||||
ChrMemoryType _chrMemoryType[0x40];
|
||||
MemoryAccessType _chrMemoryAccess[0x40];
|
||||
int32_t _chrMemoryOffset[0x100];
|
||||
ChrMemoryType _chrMemoryType[0x100];
|
||||
|
||||
vector<uint8_t> _originalPrgRom;
|
||||
vector<uint8_t> _originalChrRom;
|
||||
|
||||
protected:
|
||||
static constexpr uint32_t NametableCount = 0x10;
|
||||
static constexpr uint32_t NametableSize = 0x400;
|
||||
|
||||
RomInfo _romInfo;
|
||||
|
||||
shared_ptr<BaseControlDevice> _mapperControlDevice;
|
||||
|
@ -142,10 +143,9 @@ protected:
|
|||
|
||||
virtual void StreamState(bool saving) override;
|
||||
|
||||
void RestorePrgChrState(uint32_t* prgPages, uint32_t* chrPages);
|
||||
void RestorePrgChrState();
|
||||
|
||||
uint8_t* GetNametable(uint8_t index);
|
||||
void AddNametable(uint8_t index, uint8_t *nametable);
|
||||
uint8_t* GetNametable(uint8_t nametableIndex);
|
||||
void SetNametable(uint8_t index, uint8_t nametableIndex);
|
||||
void SetNametables(uint8_t nametable1Index, uint8_t nametable2Index, uint8_t nametable3Index, uint8_t nametable4Index);
|
||||
void SetMirroringType(MirroringType type);
|
||||
|
@ -170,7 +170,6 @@ public:
|
|||
virtual void SaveBattery() override;
|
||||
|
||||
void SetConsole(shared_ptr<Console> console);
|
||||
virtual void SetDefaultNametables(uint8_t* nametableA, uint8_t* nametableB);
|
||||
|
||||
shared_ptr<BaseControlDevice> GetMapperControlDevice();
|
||||
RomInfo GetRomInfo();
|
||||
|
|
|
@ -5,10 +5,6 @@
|
|||
//Missing Flash rom support, and only tested via a test rom
|
||||
class Cheapocabra : public BaseMapper
|
||||
{
|
||||
private:
|
||||
uint8_t _reg;
|
||||
uint8_t* _extraNametables[4];
|
||||
|
||||
protected:
|
||||
virtual uint16_t GetPRGPageSize() override { return 0x8000; }
|
||||
virtual uint16_t GetCHRPageSize() override { return 0x2000; }
|
||||
|
@ -19,47 +15,17 @@ protected:
|
|||
void InitMapper() override
|
||||
{
|
||||
AddRegisterRange(0x7000, 0x7FFF, MemoryOperation::Write);
|
||||
for(int i = 0; i < 4; i++) {
|
||||
_extraNametables[i] = new uint8_t[0x400];
|
||||
InitializeRam(_extraNametables[i], 0x400);
|
||||
AddNametable(4 + i, _extraNametables[i]);
|
||||
}
|
||||
_reg = GetPowerOnByte();
|
||||
UpdateState();
|
||||
WriteRegister(0x5000, GetPowerOnByte());
|
||||
}
|
||||
|
||||
virtual ~Cheapocabra()
|
||||
void WriteRegister(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
delete[] _extraNametables[0];
|
||||
delete[] _extraNametables[1];
|
||||
delete[] _extraNametables[2];
|
||||
delete[] _extraNametables[3];
|
||||
}
|
||||
|
||||
void StreamState(bool saving) override
|
||||
{
|
||||
BaseMapper::StreamState(saving);
|
||||
ArrayInfo<uint8_t> extraNametable0{ _extraNametables[0], 0x400 };
|
||||
ArrayInfo<uint8_t> extraNametable1{ _extraNametables[1], 0x400 };
|
||||
ArrayInfo<uint8_t> extraNametable2{ _extraNametables[2], 0x400 };
|
||||
ArrayInfo<uint8_t> extraNametable3{ _extraNametables[3], 0x400 };
|
||||
Stream(_reg, extraNametable0, extraNametable1, extraNametable2, extraNametable3);
|
||||
}
|
||||
|
||||
void UpdateState()
|
||||
{
|
||||
SelectPRGPage(0, _reg & 0x0F);
|
||||
SelectCHRPage(0, (_reg >> 4) & 0x01);
|
||||
if(_reg & 0x20) {
|
||||
SelectPRGPage(0, value & 0x0F);
|
||||
SelectCHRPage(0, (value >> 4) & 0x01);
|
||||
if(value & 0x20) {
|
||||
SetNametables(4, 5, 6, 7);
|
||||
} else {
|
||||
SetNametables(0, 1, 2, 3);
|
||||
}
|
||||
}
|
||||
|
||||
void WriteRegister(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
_reg = value;
|
||||
UpdateState();
|
||||
}
|
||||
};
|
39
Core/MMC5.h
39
Core/MMC5.h
|
@ -8,8 +8,8 @@ class MMC5 : public BaseMapper
|
|||
{
|
||||
private:
|
||||
const uint8_t NtWorkRamIndex = 4;
|
||||
const uint8_t NtEmptyIndex = 5;
|
||||
const uint8_t NtFillModeIndex = 6;
|
||||
const uint8_t NtEmptyIndex = 2;
|
||||
const uint8_t NtFillModeIndex = 3;
|
||||
|
||||
unique_ptr<MMC5Audio> _audio;
|
||||
|
||||
|
@ -18,9 +18,6 @@ private:
|
|||
|
||||
uint8_t _fillModeTile;
|
||||
uint8_t _fillModeColor;
|
||||
uint8_t *_fillModeNametable;
|
||||
|
||||
uint8_t *_emptyNametable;
|
||||
|
||||
bool _verticalSplitEnabled;
|
||||
bool _verticalSplitRightSide;
|
||||
|
@ -227,7 +224,14 @@ private:
|
|||
NtFillModeIndex //"3 - Fill-mode data"
|
||||
};
|
||||
|
||||
SetNametables(nametables[value & 0x03], nametables[(value >> 2) & 0x03], nametables[(value >> 4) & 0x03], nametables[(value >> 6) & 0x03]);
|
||||
for(int i = 0; i < 4; i++) {
|
||||
uint8_t nametableId = nametables[(value >> (i * 2)) & 0x03];
|
||||
if(nametableId == NtWorkRamIndex) {
|
||||
SetPpuMemoryMapping(0x2000+i*0x400, 0x2000+i*0x400+0x3FF, _workRam, MemoryAccessType::ReadWrite);
|
||||
} else {
|
||||
SetNametable(i, nametableId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SetExtendedRamMode(uint8_t mode)
|
||||
|
@ -252,13 +256,13 @@ private:
|
|||
void SetFillModeTile(uint8_t tile)
|
||||
{
|
||||
_fillModeTile = tile;
|
||||
memset(_fillModeNametable, tile, 32 * 30); //32 tiles per row, 30 rows
|
||||
memset(GetNametable(NtFillModeIndex), tile, 32 * 30); //32 tiles per row, 30 rows
|
||||
}
|
||||
|
||||
void SetFillModeColor(uint8_t color)
|
||||
{
|
||||
_fillModeColor = color;
|
||||
memset(_fillModeNametable + 32 * 30, color, 64); //Attribute table is 64 bytes
|
||||
memset(GetNametable(NtFillModeIndex) + 32 * 30, color, 64); //Attribute table is 64 bytes
|
||||
}
|
||||
|
||||
bool IsSpriteFetch()
|
||||
|
@ -317,18 +321,11 @@ protected:
|
|||
_splitTile = 0;
|
||||
_splitTileNumber = -1;
|
||||
|
||||
_fillModeNametable = new uint8_t[0x400];
|
||||
_emptyNametable = new uint8_t[0x400];
|
||||
InitializeRam(_emptyNametable, 0x400);
|
||||
InitializeRam(_fillModeNametable, 0x400);
|
||||
memset(GetNametable(NtEmptyIndex), 0, BaseMapper::NametableSize);
|
||||
|
||||
//"Expansion RAM ($5C00-$5FFF, read/write)"
|
||||
SetCpuMemoryMapping(0x5C00, 0x5FFF, 0, PrgMemoryType::WorkRam);
|
||||
|
||||
AddNametable(NtWorkRamIndex, _workRam);
|
||||
AddNametable(NtEmptyIndex, _emptyNametable);
|
||||
AddNametable(NtFillModeIndex, _fillModeNametable);
|
||||
|
||||
//"Additionally, Romance of the 3 Kingdoms 2 seems to expect it to be in 8k PRG mode ($5100 = $03)."
|
||||
WriteRegister(0x5100, 0x03);
|
||||
|
||||
|
@ -336,29 +333,23 @@ protected:
|
|||
WriteRegister(0x5117, 0xFF);
|
||||
}
|
||||
|
||||
virtual ~MMC5()
|
||||
{
|
||||
delete[] _fillModeNametable;
|
||||
delete[] _emptyNametable;
|
||||
}
|
||||
|
||||
void StreamState(bool saving) override
|
||||
{
|
||||
BaseMapper::StreamState(saving);
|
||||
|
||||
ArrayInfo<uint8_t> prgBanks = { _prgBanks, 5 };
|
||||
ArrayInfo<uint16_t> chrBanks = { _chrBanks, 12 };
|
||||
ArrayInfo<uint8_t> fillModeNametable = { _fillModeNametable, 0x400 };
|
||||
SnapshotInfo audio{ _audio.get() };
|
||||
Stream(_prgRamProtect1, _prgRamProtect2, _fillModeTile, _fillModeColor, _verticalSplitEnabled, _verticalSplitRightSide,
|
||||
_verticalSplitDelimiterTile, _verticalSplitScroll, _verticalSplitBank, _multiplierValue1, _multiplierValue2,
|
||||
_nametableMapping, _extendedRamMode, _exAttributeLastNametableFetch, _exAttrLastFetchCounter, _exAttrSelectedChrBank,
|
||||
_prgMode, prgBanks, _chrMode, _chrUpperBits, chrBanks, _lastChrReg,
|
||||
_spriteFetch, _largeSprites, _irqCounterTarget, _irqEnabled, _previousScanline, _irqCounter, _irqPending, _ppuInFrame, audio, fillModeNametable,
|
||||
_spriteFetch, _largeSprites, _irqCounterTarget, _irqEnabled, _previousScanline, _irqCounter, _irqPending, _ppuInFrame, audio,
|
||||
_splitInSplitRegion, _splitVerticalScroll, _splitTile, _splitTileNumber, _lastVramOperationType);
|
||||
|
||||
if(!saving) {
|
||||
UpdatePrgBanks();
|
||||
SetNametableMapping(_nametableMapping);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,11 +14,6 @@ protected:
|
|||
if(GetMirroringType() == MirroringType::FourScreens) {
|
||||
SetMirroringType(_romInfo.NesHeader.Byte6 & 0x01 ? MirroringType::ScreenBOnly : MirroringType::ScreenAOnly);
|
||||
}
|
||||
}
|
||||
|
||||
void SetDefaultNametables(uint8_t* nametableA, uint8_t* nametableB) override
|
||||
{
|
||||
BaseMapper::SetDefaultNametables(nametableA, nametableB);
|
||||
|
||||
uint16_t mask = 0;
|
||||
switch(GetMirroringType()) {
|
||||
|
@ -30,7 +25,7 @@ protected:
|
|||
}
|
||||
|
||||
for(int i = 0; i < 8; i++) {
|
||||
SetPpuMemoryMapping(i * 0x400, i * 0x400 + 0x3FF, (i * 0x400) & mask ? GetNametable(1) : GetNametable(0));
|
||||
SetPpuMemoryMapping(i*0x400, i*0x400+0x3FF, ((i * 0x400) & mask) ? 1 : 0, ChrMemoryType::NametableRam);
|
||||
}
|
||||
}
|
||||
};
|
|
@ -11,11 +11,6 @@ MemoryManager::MemoryManager(shared_ptr<Console> console)
|
|||
_internalRAM = new uint8_t[InternalRAMSize];
|
||||
_internalRamHandler.SetInternalRam(_internalRAM);
|
||||
|
||||
for(int i = 0; i < 2; i++) {
|
||||
_nametableRAM[i] = new uint8_t[NameTableScreenSize];
|
||||
_console->GetMapper()->InitializeRam(_nametableRAM[i], NameTableScreenSize);
|
||||
}
|
||||
|
||||
_ramReadHandlers = new IMemoryHandler*[RAMSize];
|
||||
_ramWriteHandlers = new IMemoryHandler*[RAMSize];
|
||||
|
||||
|
@ -30,9 +25,6 @@ MemoryManager::MemoryManager(shared_ptr<Console> console)
|
|||
MemoryManager::~MemoryManager()
|
||||
{
|
||||
delete[] _internalRAM;
|
||||
for(int i = 0; i < 2; i++) {
|
||||
delete[] _nametableRAM[i];
|
||||
}
|
||||
|
||||
delete[] _ramReadHandlers;
|
||||
delete[] _ramWriteHandlers;
|
||||
|
@ -41,7 +33,6 @@ MemoryManager::~MemoryManager()
|
|||
void MemoryManager::SetMapper(shared_ptr<BaseMapper> mapper)
|
||||
{
|
||||
_mapper = mapper;
|
||||
_mapper->SetDefaultNametables(_nametableRAM[0], _nametableRAM[1]);
|
||||
}
|
||||
|
||||
void MemoryManager::Reset(bool softReset)
|
||||
|
@ -165,9 +156,7 @@ uint32_t MemoryManager::ToAbsolutePrgAddress(uint16_t ramAddr)
|
|||
void MemoryManager::StreamState(bool saving)
|
||||
{
|
||||
ArrayInfo<uint8_t> internalRam = { _internalRAM, MemoryManager::InternalRAMSize };
|
||||
ArrayInfo<uint8_t> nameTable0Ram = { _nametableRAM[0], MemoryManager::NameTableScreenSize };
|
||||
ArrayInfo<uint8_t> nameTable1Ram = { _nametableRAM[1], MemoryManager::NameTableScreenSize };
|
||||
Stream(internalRam, nameTable0Ram, nameTable1Ram);
|
||||
Stream(internalRam);
|
||||
}
|
||||
|
||||
uint8_t MemoryManager::GetOpenBus(uint8_t mask)
|
||||
|
|
|
@ -14,13 +14,11 @@ class MemoryManager : public Snapshotable
|
|||
private:
|
||||
static constexpr int RAMSize = 0x10000;
|
||||
static constexpr int VRAMSize = 0x4000;
|
||||
static constexpr int NameTableScreenSize = 0x400;
|
||||
|
||||
shared_ptr<Console> _console;
|
||||
shared_ptr<BaseMapper> _mapper;
|
||||
|
||||
uint8_t *_internalRAM;
|
||||
uint8_t *_nametableRAM[2];
|
||||
|
||||
OpenBusHandler _openBusHandler;
|
||||
InternalRamHandler<0x7FF> _internalRamHandler;
|
||||
|
|
|
@ -169,7 +169,7 @@ protected:
|
|||
case 0x8000: case 0x8800: case 0x9000: case 0x9800: {
|
||||
uint8_t bankNumber = (addr - 0x8000) >> 11;
|
||||
if(!_lowChrNtMode && value >= 0xE0 && _variant == NamcoVariant::Namco163) {
|
||||
SelectCHRPage(bankNumber, (value & 0x01) == 0x01 ? ChrSpecialPage::NametableB : ChrSpecialPage::NametableA);
|
||||
SelectCHRPage(bankNumber, value & 0x01, ChrMemoryType::NametableRam);
|
||||
} else {
|
||||
SelectCHRPage(bankNumber, value);
|
||||
}
|
||||
|
@ -179,7 +179,7 @@ protected:
|
|||
case 0xA000: case 0xA800: case 0xB000: case 0xB800: {
|
||||
uint8_t bankNumber = ((addr - 0xA000) >> 11) + 4;
|
||||
if(!_highChrNtMode && value >= 0xE0 && _variant == NamcoVariant::Namco163) {
|
||||
SelectCHRPage(bankNumber, (value & 0x01) == 0x01 ? ChrSpecialPage::NametableB : ChrSpecialPage::NametableA);
|
||||
SelectCHRPage(bankNumber, value & 0x01, ChrMemoryType::NametableRam);
|
||||
} else {
|
||||
SelectCHRPage(bankNumber, value);
|
||||
}
|
||||
|
@ -199,7 +199,7 @@ protected:
|
|||
} else {
|
||||
uint8_t bankNumber = ((addr - 0xC000) >> 11) + 8;
|
||||
if(value >= 0xE0) {
|
||||
SelectCHRPage(bankNumber, (value & 0x01) == 0x01 ? ChrSpecialPage::NametableB : ChrSpecialPage::NametableA);
|
||||
SelectCHRPage(bankNumber, value & 0x01, ChrMemoryType::NametableRam);
|
||||
} else {
|
||||
SelectCHRPage(bankNumber, value);
|
||||
}
|
||||
|
|
|
@ -118,7 +118,7 @@ protected:
|
|||
|
||||
ArrayInfo<uint8_t> internalRam{ _internalRam, 0x80 };
|
||||
ArrayInfo<int16_t> channelOutput{ _channelOutput, 8 };
|
||||
Stream(internalRam, channelOutput, _ramPosition, _autoIncrement, _updateCounter, _currentChannel, _lastOutput);
|
||||
Stream(internalRam, channelOutput, _ramPosition, _autoIncrement, _updateCounter, _currentChannel, _lastOutput, _disableSound);
|
||||
}
|
||||
|
||||
void ClockAudio() override
|
||||
|
|
|
@ -129,27 +129,18 @@ bool SaveStateManager::LoadState(istream &stream, bool hashCheckRequired)
|
|||
}
|
||||
|
||||
stream.read((char*)&fileFormatVersion, sizeof(fileFormatVersion));
|
||||
if(fileFormatVersion < 5) {
|
||||
if(fileFormatVersion < 10) {
|
||||
MessageManager::DisplayMessage("SaveStates", "SaveStateIncompatibleVersion");
|
||||
return false;
|
||||
} else if(fileFormatVersion == 5) {
|
||||
//No SHA1 field in version 5
|
||||
if(hashCheckRequired) {
|
||||
//Can't manually load < v5 save states, since we can't know what game the save state is for
|
||||
MessageManager::DisplayMessage("SaveStates", "SaveStateIncompatibleVersion");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
int32_t mapperId = -1;
|
||||
int32_t subMapperId = -1;
|
||||
if(fileFormatVersion >= 8) {
|
||||
uint16_t id;
|
||||
uint8_t sid;
|
||||
stream.read((char*)&id, sizeof(uint16_t));
|
||||
stream.read((char*)&sid, sizeof(uint8_t));
|
||||
mapperId = id;
|
||||
subMapperId = sid;
|
||||
}
|
||||
|
||||
char hash[41] = {};
|
||||
stream.read(hash, 40);
|
||||
|
|
|
@ -14,7 +14,7 @@ private:
|
|||
string GetStateFilepath(int stateIndex);
|
||||
|
||||
public:
|
||||
static constexpr uint32_t FileFormatVersion = 9;
|
||||
static constexpr uint32_t FileFormatVersion = 10;
|
||||
|
||||
SaveStateManager(shared_ptr<Console> console);
|
||||
|
||||
|
|
|
@ -15,16 +15,18 @@ private:
|
|||
|
||||
void UpdateNametables()
|
||||
{
|
||||
AddNametable(4, _chrRom + _ntRegs[0] * 0x400);
|
||||
AddNametable(5, _chrRom + _ntRegs[1] * 0x400);
|
||||
|
||||
if(_useChrForNametables) {
|
||||
for(int i = 0; i < 4; i++) {
|
||||
uint8_t reg = 0;
|
||||
switch(GetMirroringType()) {
|
||||
case MirroringType::FourScreens: break; //4-screen mirroring is not supported by this mapper
|
||||
case MirroringType::Vertical: SetNametables(4, 5, 4, 5); break;
|
||||
case MirroringType::Horizontal: SetNametables(4, 4, 5, 5); break;
|
||||
case MirroringType::ScreenAOnly: SetNametables(4, 4, 4, 4); break;
|
||||
case MirroringType::ScreenBOnly: SetNametables(5, 5, 5, 5); break;
|
||||
case MirroringType::Vertical: reg = i & 0x01; break;
|
||||
case MirroringType::Horizontal: reg = (i & 0x02) >> 1; break;
|
||||
case MirroringType::ScreenAOnly: reg = 0; break;
|
||||
case MirroringType::ScreenBOnly: reg = 1; break;
|
||||
}
|
||||
|
||||
SetPpuMemoryMapping(0x2000+i*0x400, 0x2000+i*0x400+0x3FF, ChrMemoryType::Default, _ntRegs[reg] * 0x400, _chrRamSize > 0 ? MemoryAccessType::ReadWrite : MemoryAccessType::Read);
|
||||
}
|
||||
} else {
|
||||
//Reset to default mirroring
|
||||
|
@ -57,11 +59,6 @@ protected:
|
|||
BaseMapper::StreamState(saving);
|
||||
|
||||
Stream(_ntRegs[0], _ntRegs[1], _useChrForNametables, _prgRamEnabled, _usingExternalRom, _externalPage);
|
||||
|
||||
if(!saving) {
|
||||
UpdateNametables();
|
||||
UpdateState();
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateState()
|
||||
|
|
11
Core/Types.h
11
Core/Types.h
|
@ -78,7 +78,8 @@ enum class ChrMemoryType
|
|||
{
|
||||
Default,
|
||||
ChrRom,
|
||||
ChrRam
|
||||
ChrRam,
|
||||
NametableRam
|
||||
};
|
||||
|
||||
enum MemoryAccessType
|
||||
|
@ -90,12 +91,6 @@ enum MemoryAccessType
|
|||
ReadWrite = 0x03
|
||||
};
|
||||
|
||||
enum ChrSpecialPage
|
||||
{
|
||||
NametableA = 0x7000,
|
||||
NametableB = 0x7001
|
||||
};
|
||||
|
||||
enum class MirroringType
|
||||
{
|
||||
Horizontal,
|
||||
|
@ -123,8 +118,6 @@ struct CartridgeState
|
|||
ChrMemoryType ChrType[0x40];
|
||||
MemoryAccessType ChrMemoryAccess[0x40];
|
||||
|
||||
uint32_t Nametables[8];
|
||||
|
||||
uint32_t WorkRamPageSize;
|
||||
uint32_t SaveRamPageSize;
|
||||
|
||||
|
|
|
@ -24,15 +24,11 @@ protected:
|
|||
} else {
|
||||
_enableMirroringBit = GetMirroringType() == MirroringType::FourScreens;
|
||||
}
|
||||
}
|
||||
|
||||
void SetDefaultNametables(uint8_t* nametableA, uint8_t* nametableB) override
|
||||
{
|
||||
BaseMapper::SetDefaultNametables(nametableA, nametableB);
|
||||
if(GetMirroringType() == MirroringType::FourScreens && _chrRam && _chrRamSize >= 0x8000) {
|
||||
//InfiniteNesLives four-screen mirroring variation, last 8kb of CHR RAM is always mapped to 0x2000-0x3FFF (0x3EFF due to palette)
|
||||
//This "breaks" the "UNROM512_4screen_test" test ROM - was the ROM actually tested on this board? Seems to contradict hardware specs
|
||||
SetPpuMemoryMapping(0x2000, 0x3FFF, _chrRam + 0x6000);
|
||||
SetPpuMemoryMapping(0x2000, 0x3FFF, ChrMemoryType::ChrRam, 0x6000, MemoryAccessType::ReadWrite);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -135,6 +135,11 @@ namespace Mesen.GUI.Debugger.Controls
|
|||
|
||||
if(memoryType == null) {
|
||||
regions.Add(new MemoryRegionInfo() { Name = "N/A", Size = currentSize, Color = Color.FromArgb(222, 222, 222) });
|
||||
} else if(memoryType == ChrMemoryType.NametableRam) {
|
||||
int page = (int)(state.ChrMemoryOffset[startIndex] / 0x400);
|
||||
Color color = alternateColor ? Color.FromArgb(0xF4, 0xC7, 0xD4) : Color.FromArgb(0xD4, 0xA7, 0xB4);
|
||||
alternateColor = !alternateColor;
|
||||
regions.Add(new MemoryRegionInfo() { Name = "NT" + page.ToString(), Size = currentSize, Color = color });
|
||||
} else if(memoryType == ChrMemoryType.ChrRom || memoryType == ChrMemoryType.Default && state.ChrRomSize > 0) {
|
||||
int page = (int)(state.ChrMemoryOffset[startIndex] / state.ChrPageSize);
|
||||
Color color = alternateColor ? Color.FromArgb(0xC4, 0xE7, 0xD4) : Color.FromArgb(0xA4, 0xD7, 0xB4);
|
||||
|
@ -150,9 +155,16 @@ namespace Mesen.GUI.Debugger.Controls
|
|||
startIndex = i;
|
||||
};
|
||||
|
||||
for(int i = 0; i < 0x20; i++) {
|
||||
for(int i = 0; i < 0x30; i++) {
|
||||
if(state.ChrMemoryAccess[i] != MemoryAccessType.NoAccess) {
|
||||
bool forceNewBlock = (i - startIndex) << 8 >= state.ChrPageSize;
|
||||
bool forceNewBlock = false;
|
||||
int blockSize = (i - startIndex) << 8;
|
||||
if(memoryType == ChrMemoryType.NametableRam && blockSize >= 0x400) {
|
||||
forceNewBlock = true;
|
||||
} else if(memoryType != ChrMemoryType.NametableRam && blockSize >= state.ChrPageSize) {
|
||||
forceNewBlock = true;
|
||||
}
|
||||
|
||||
if(forceNewBlock || memoryType != state.ChrMemoryType[i] || state.ChrMemoryOffset[i] - state.ChrMemoryOffset[i - 1] != 0x100) {
|
||||
addSection(i);
|
||||
}
|
||||
|
@ -169,10 +181,6 @@ namespace Mesen.GUI.Debugger.Controls
|
|||
}
|
||||
addSection(-1);
|
||||
|
||||
for(int i = 0; i < 4; i++) {
|
||||
regions.Add(new MemoryRegionInfo() { Name = "NT " + state.Nametables[i].ToString(), Size = 0x400, Color = i % 2 == 0 ? Color.FromArgb(0xF4, 0xC7, 0xD4) : Color.FromArgb(0xD4, 0xA7, 0xB4) });
|
||||
}
|
||||
|
||||
UpdateRegionArray(regions);
|
||||
}
|
||||
|
||||
|
|
|
@ -1227,7 +1227,8 @@ namespace Mesen.GUI
|
|||
{
|
||||
Default,
|
||||
ChrRom,
|
||||
ChrRam
|
||||
ChrRam,
|
||||
NametableRam
|
||||
}
|
||||
|
||||
public enum MemoryAccessType
|
||||
|
@ -1290,9 +1291,6 @@ namespace Mesen.GUI
|
|||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x40)]
|
||||
public MemoryAccessType[] ChrMemoryAccess;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
||||
public UInt32[] Nametables;
|
||||
|
||||
public UInt32 WorkRamPageSize;
|
||||
public UInt32 SaveRamPageSize;
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue