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;
+ }
+ }
+};