diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index be7d5dcc..e6534f58 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -903,6 +903,8 @@ + + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index 83c58224..c395fed7 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -1432,6 +1432,12 @@ Nes\Mappers\Unif + + Nes\Mappers\Unif + + + Nes\Mappers\Unif + diff --git a/Core/MapperFactory.cpp b/Core/MapperFactory.cpp index e6429779..6f010633 100644 --- a/Core/MapperFactory.cpp +++ b/Core/MapperFactory.cpp @@ -244,6 +244,7 @@ #include "Unl43272.h" #include "Unl8237A.h" #include "UnlD1038.h" +#include "UnlDripGame.h" #include "UnlPci556.h" #include "UnlPuzzle.h" #include "UnlVrc7.h" @@ -273,18 +274,18 @@ Supported mappers: ----------------------------------------------------------------- | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10| 11| 12| 13| 14| 15| | 16| 17| 18| 19|...| 21| 22| 23| 24| 25| 26| 27| 28| 29| 30| 31| -| 32| 33| 34| 35| 36| 37| 38|---| 40| 41| 42| 43| 44| 45| 46| 47| +| 32| 33| 34| 35| 36| 37| 38| 39| 40| 41| 42| 43| 44| 45| 46| 47| | 48| 49| 50| 51| 52| 53| 54|???| 56| 57| 58|===| 60| 61| 62| 63| | 64| 65| 66| 67| 68| 69| 70| 71| 72| 73| 74| 75| 76| 77| 78| 79| | 80|===| 82| 83|===| 85| 86| 87| 88| 89| 90| 91| 92| 93| 94| 95| | 96| 97|===| 99|...|101|===|103|104|105|106|107|108|===|===|111| -|112|113|114|115| |117|118|119|120|121|===|123|===|125|126|===| +|112|113|114|115|116|117|118|119|120|121|===|123|===|125|126|===| |===|===|===|===|132|133|134|===|136|137|138|139|140|141|142|143| |144|145|146|147|148|149|150|151|152|153|154|155|156|157|158|159| |---|===|162|163|164|165|166|167|168|===|170|171|172|173|174|175| |176|177|178|179|180|---|182|183|184|185|186|187|188|189|190|191| -|192|193|194|195|196|197| |199|200|201|202|203|204|205|206|207| -|???|209|210|211|212|213|214|215|216|217|218|219|220|221|222|???| +|192|193|194|195|196|197|198|199|200|201|202|203|204|205|206|207| +|208|209|210|211|212|213|214|215|216|217|218|219|220|221|222|???| |???|225|226|227|228|229|230|231|232|233|234|235|236|===|238|===| |240|241|242|243|244|245|246|===|===|249|250|===|252|253|254|255| ----------------------------------------------------------------- @@ -592,6 +593,7 @@ BaseMapper* MapperFactory::GetMapperFromID(RomData &romData) case UnifBoards::Unl43272: return new Unl43272(); case UnifBoards::Unl8237A: return new Unl8237A(); case UnifBoards::UnlD1038: return new UnlD1038(); + case UnifBoards::UnlDripGame: return new UnlDripGame(); case UnifBoards::UnlPuzzle: return new UnlPuzzle(); case UnifBoards::UnlVrc7: return new UnlVrc7(); case UnifBoards::Yoko: return new Yoko(); diff --git a/Core/UnifBoards.h b/Core/UnifBoards.h index a402d720..29705f22 100644 --- a/Core/UnifBoards.h +++ b/Core/UnifBoards.h @@ -70,5 +70,6 @@ namespace UnifBoards { BmcHpxx, DragonFighter, BmcGn45, + UnlDripGame, }; } \ No newline at end of file diff --git a/Core/UnifLoader.cpp b/Core/UnifLoader.cpp index b82d691b..a8ace995 100644 --- a/Core/UnifLoader.cpp +++ b/Core/UnifLoader.cpp @@ -160,4 +160,5 @@ std::unordered_map UnifLoader::_boardMappings = std::unordered_map< { "WAIXING-FS005", UnifBoards::UnknownBoard }, { "HPxx", UnifBoards::BmcHpxx }, { "GN-45", UnifBoards::BmcGn45 }, //Doesn't actually exist as a UNIF file (used to assign a mapper to GN-45 boards) + { "DRIPGAME", UnifBoards::UnlDripGame }, }; \ No newline at end of file diff --git a/Core/UnlDripGame.h b/Core/UnlDripGame.h new file mode 100644 index 00000000..ddabbdbb --- /dev/null +++ b/Core/UnlDripGame.h @@ -0,0 +1,169 @@ +#pragma once +#include "stdafx.h" +#include "BaseMapper.h" +#include "UnlDripGameAudio.h" + +class UnlDripGame : public BaseMapper +{ +private: + UnlDripGameAudio _audioChannels[2]; + uint8_t _extendedAttributes[2][0x400]; + uint8_t _lowByteIrqCounter; + uint16_t _irqCounter; + uint16_t _lastNametableFetchAddr; + bool _irqEnabled; + bool _extAttributesEnabled; + bool _wramWriteEnabled; + bool _dipSwitch; + +protected: + 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 + { + _lowByteIrqCounter = 0; + _irqCounter = 0; + _irqEnabled = false; + _extAttributesEnabled = false; + _wramWriteEnabled = false; + _dipSwitch = false; + _lastNametableFetchAddr = 0; + + InitializeRam(_extendedAttributes[0], 0x400); + 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 extAttributes1 { _extendedAttributes[0], 0x400 }; + ArrayInfo extAttributes2 { _extendedAttributes[1], 0x400 }; + SnapshotInfo audioChannel1 { &_audioChannels[0] }; + SnapshotInfo audioChannel2 { &_audioChannels[1] }; + + Stream(extAttributes1, extAttributes2, audioChannel1, audioChannel2, _lowByteIrqCounter, _irqCounter, _irqEnabled, + _extAttributesEnabled, _wramWriteEnabled, _dipSwitch); + + 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; + CPU::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()) { + 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 (_dipSwitch ? 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. + CPU::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; + } + } +}; diff --git a/Core/UnlDripGameAudio.h b/Core/UnlDripGameAudio.h new file mode 100644 index 00000000..80f88b59 --- /dev/null +++ b/Core/UnlDripGameAudio.h @@ -0,0 +1,135 @@ +#include "BaseExpansionAudio.h" + +class UnlDripGameAudio : public BaseExpansionAudio +{ +private: + uint8_t _buffer[256]; + uint8_t _readPos; + uint8_t _writePos; + bool _bufferFull; + bool _bufferEmpty; + + uint16_t _freq; + uint16_t _timer; + uint8_t _volume; + int16_t _prevOutput; + +protected: + void StreamState(bool saving) override + { + BaseExpansionAudio::StreamState(saving); + ArrayInfo buffer { _buffer, 256 }; + Stream(_readPos, _writePos, _bufferFull, _bufferEmpty, _freq, _timer, _volume, _prevOutput, buffer); + } + + void ClockAudio() override + { + if(_bufferEmpty) { + return; + } + + _timer--; + if(_timer == 0) { + //Each time the timer reaches zero, it is reloaded and a byte is removed from the + //channel's FIFO and is output (with 0x80 being the 'center' voltage) at the + //channel's specified volume. + _timer = _freq; + + if(_readPos == _writePos) { + _bufferFull = false; + } + + _readPos++; + SetOutput(((int)_buffer[_readPos] - 0x80) * _volume); + + if(_readPos == _writePos) { + _bufferEmpty = true; + } + } + } + + void SetOutput(int16_t output) + { + APU::AddExpansionAudioDelta(AudioChannel::VRC7, (output - _prevOutput) * 3); + _prevOutput = output; + } + + void ResetBuffer() + { + memset(_buffer, 0, 256); + _readPos = 0; + _writePos = 0; + _bufferFull = false; + _bufferEmpty = true; + } + +public: + UnlDripGameAudio() + { + _freq = 0; + _timer = 0; + _volume = 0; + _prevOutput = 0; + ResetBuffer(); + } + + uint8_t ReadRegister() + { + uint8_t result = 0; + if(_bufferFull) { + result |= 0x80; + } + if(_bufferEmpty) { + result |= 0x40; + } + return result; + } + + void WriteRegister(uint16_t addr, uint8_t value) + { + switch(addr & 0x03) { + case 0: + //Writing any value will silence the corresponding sound channel + //When a channel's Clear FIFO register is written to, its timer is reset to the + //last written frequency and it is silenced, outputting a 'center' voltage. + ResetBuffer(); + SetOutput(0); + _timer = _freq; + break; + + case 1: + //Writing a value will insert it into the FIFO. + if(_readPos == _writePos) { + //When data is written to an empty channel's Data Port, the channel's timer is + //reloaded from the Period registers and playback begins immediately. + _bufferEmpty = false; + SetOutput((value - 0x80) * _volume); + _timer = _freq; + } + + _buffer[_writePos++] = value; + if(_readPos == _writePos) { + _bufferFull = true; + } + break; + + case 2: + //Specifies channel playback rate, in cycles per sample (lower 8 bits) + _freq = (_freq & 0x0F00) | value; + break; + + case 3: + //Specifies channel playback rate, in cycles per sample (higher 8 bits) (bits 0-3) + //Specifies channel playback volume (bits 4-7) + _freq = (_freq & 0xFF) | ((value & 0x0F) << 8); + _volume = (value & 0xF0) >> 4; + + if(!_bufferEmpty) { + //Updates to a channel's Period do not take effect until the current + //sample has finished playing, but updates to a channel's Volume take effect immediately. + SetOutput(((int)_buffer[_readPos] - 0x80) * _volume); + } + break; + } + } +};