172 lines
5.6 KiB
C++
172 lines
5.6 KiB
C++
#pragma once
|
|
#include "stdafx.h"
|
|
#include "BaseMapper.h"
|
|
#include "UnlDripGameAudio.h"
|
|
|
|
class UnlDripGame : public BaseMapper
|
|
{
|
|
private:
|
|
unique_ptr<UnlDripGameAudio> _audioChannels[2];
|
|
uint8_t _extendedAttributes[2][0x400];
|
|
uint8_t _lowByteIrqCounter;
|
|
uint16_t _irqCounter;
|
|
uint16_t _lastNametableFetchAddr;
|
|
bool _irqEnabled;
|
|
bool _extAttributesEnabled;
|
|
bool _wramWriteEnabled;
|
|
|
|
protected:
|
|
uint32_t GetDipSwitchCount() override { return 1; }
|
|
uint16_t GetPRGPageSize() override { return 0x4000; }
|
|
uint16_t GetCHRPageSize() override { return 0x800; }
|
|
bool AllowRegisterRead() override { return true; }
|
|
uint16_t RegisterStartAddress() override { return 0x8000; }
|
|
uint16_t RegisterEndAddress() override { return 0xFFFF; }
|
|
|
|
void InitMapper() override
|
|
{
|
|
_audioChannels[0].reset(new UnlDripGameAudio(_console));
|
|
_audioChannels[1].reset(new UnlDripGameAudio(_console));
|
|
|
|
_lowByteIrqCounter = 0;
|
|
_irqCounter = 0;
|
|
_irqEnabled = false;
|
|
_extAttributesEnabled = false;
|
|
_wramWriteEnabled = false;
|
|
_lastNametableFetchAddr = 0;
|
|
|
|
_console->InitializeRam(_extendedAttributes[0], 0x400);
|
|
_console->InitializeRam(_extendedAttributes[1], 0x400);
|
|
|
|
SelectPRGPage(1, -1);
|
|
|
|
AddRegisterRange(0x4800, 0x5FFF, MemoryOperation::Read);
|
|
RemoveRegisterRange(0x8000, 0xFFFF, MemoryOperation::Read);
|
|
}
|
|
|
|
void StreamState(bool saving) override
|
|
{
|
|
BaseMapper::StreamState(saving);
|
|
|
|
ArrayInfo<uint8_t> extAttributes1 { _extendedAttributes[0], 0x400 };
|
|
ArrayInfo<uint8_t> extAttributes2 { _extendedAttributes[1], 0x400 };
|
|
SnapshotInfo audioChannel1 { _audioChannels[0].get() };
|
|
SnapshotInfo audioChannel2 { _audioChannels[1].get() };
|
|
|
|
Stream(extAttributes1, extAttributes2, audioChannel1, audioChannel2, _lowByteIrqCounter, _irqCounter, _irqEnabled,
|
|
_extAttributesEnabled, _wramWriteEnabled);
|
|
|
|
if(!saving) {
|
|
UpdateWorkRamState();
|
|
}
|
|
}
|
|
|
|
void ProcessCpuClock() override
|
|
{
|
|
if(_irqEnabled) {
|
|
if(_irqCounter > 0) {
|
|
_irqCounter--;
|
|
if(_irqCounter == 0) {
|
|
//While the IRQ counter is enabled, the timer is decremented once per CPU
|
|
//cycle.Once the timer reaches zero, the /IRQ line is set to logic 0 and the
|
|
//timer stops decrementing
|
|
_irqEnabled = false;
|
|
_console->GetCpu()->SetIrqSource(IRQSource::External);
|
|
}
|
|
}
|
|
}
|
|
|
|
_audioChannels[0]->Clock();
|
|
_audioChannels[1]->Clock();
|
|
}
|
|
|
|
void UpdateWorkRamState()
|
|
{
|
|
SetCpuMemoryMapping(0x6000, 0x7FFF, 0, PrgMemoryType::WorkRam, _wramWriteEnabled ? MemoryAccessType::ReadWrite : MemoryAccessType::Read);
|
|
}
|
|
|
|
virtual uint8_t MapperReadVRAM(uint16_t addr, MemoryOperationType memoryOperationType) override
|
|
{
|
|
if(_extAttributesEnabled && memoryOperationType == MemoryOperationType::PpuRenderingRead) {
|
|
if(addr >= 0x2000 && (addr & 0x3FF) < 0x3C0) {
|
|
//Nametable fetches
|
|
_lastNametableFetchAddr = addr & 0x03FF;
|
|
} else if(addr >= 0x2000 && (addr & 0x3FF) >= 0x3C0) {
|
|
//Attribute fetches
|
|
uint8_t bank;
|
|
switch(GetMirroringType()) {
|
|
default:
|
|
case MirroringType::ScreenAOnly: bank = 0; break;
|
|
case MirroringType::ScreenBOnly: bank = 1; break;
|
|
case MirroringType::Horizontal: bank = (addr & 0x800) ? 1 : 0; break;
|
|
case MirroringType::Vertical: bank = (addr & 0x400) ? 1 : 0; break;
|
|
}
|
|
|
|
//Return a byte containing the same palette 4 times - this allows the PPU to select the right palette no matter the shift value
|
|
uint8_t value = _extendedAttributes[bank][_lastNametableFetchAddr & 0x3FF] & 0x03;
|
|
return (value << 6) | (value << 4) | (value << 2) | value;
|
|
}
|
|
}
|
|
return BaseMapper::MapperReadVRAM(addr, memoryOperationType);
|
|
}
|
|
|
|
uint8_t ReadRegister(uint16_t addr) override
|
|
{
|
|
switch(addr & 0x5800) {
|
|
case 0x4800: return (GetDipSwitches() ? 0x80 : 0) | 0x64;
|
|
case 0x5000: return _audioChannels[0]->ReadRegister();
|
|
case 0x5800: return _audioChannels[1]->ReadRegister();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void WriteRegister(uint16_t addr, uint8_t value) override
|
|
{
|
|
if(addr <= 0xBFFF) {
|
|
switch(addr & 0x800F) {
|
|
case 0x8000: case 0x8001: case 0x8002: case 0x8003:
|
|
_audioChannels[0]->WriteRegister(addr, value);
|
|
break;
|
|
|
|
case 0x8004: case 0x8005: case 0x8006: case 0x8007:
|
|
_audioChannels[1]->WriteRegister(addr, value);
|
|
break;
|
|
|
|
case 0x8008:
|
|
_lowByteIrqCounter = value;
|
|
break;
|
|
|
|
case 0x8009:
|
|
//Data written to the IRQ Counter Low register is buffered until writing to IRQ
|
|
//Counter High, at which point the composite data is written directly to the IRQ timer.
|
|
_irqCounter = ((value & 0x7F) << 8) | _lowByteIrqCounter;
|
|
_irqEnabled = (value & 0x80) != 0;
|
|
|
|
//Writing to the IRQ Enable register will acknowledge the interrupt and return the /IRQ signal to logic 1.
|
|
_console->GetCpu()->ClearIrqSource(IRQSource::External);
|
|
break;
|
|
|
|
case 0x800A:
|
|
switch(value & 0x03) {
|
|
case 0: SetMirroringType(MirroringType::Vertical); break;
|
|
case 1: SetMirroringType(MirroringType::Horizontal); break;
|
|
case 2: SetMirroringType(MirroringType::ScreenAOnly); break;
|
|
case 3: SetMirroringType(MirroringType::ScreenBOnly); break;
|
|
}
|
|
_extAttributesEnabled = (value & 0x04) != 0;
|
|
_wramWriteEnabled = (value & 0x08) != 0;
|
|
UpdateWorkRamState();
|
|
break;
|
|
|
|
case 0x800B: SelectPRGPage(0, value & 0x0F); break;
|
|
case 0x800C: SelectCHRPage(0, value & 0x0F); break;
|
|
case 0x800D: SelectCHRPage(1, value & 0x0F); break;
|
|
case 0x800E: SelectCHRPage(2, value & 0x0F); break;
|
|
case 0x800F: SelectCHRPage(3, value & 0x0F); break;
|
|
}
|
|
} else {
|
|
//Attribute expansion memory at $C000-$C7FF is mirrored throughout $C000-$FFFF.
|
|
_extendedAttributes[(addr & 0x400) ? 1 : 0][addr & 0x3FF] = value;
|
|
}
|
|
}
|
|
};
|