UNIF: Added support for UNL-DRIPGAME boards

This commit is contained in:
Sour 2018-06-24 12:04:38 -04:00
parent c2e063d5c9
commit cef7fa9eaa
7 changed files with 320 additions and 4 deletions

View file

@ -903,6 +903,8 @@
<ClInclude Include="UnifLoader.h" />
<ClInclude Include="Unl255in1.h" />
<ClInclude Include="Unl43272.h" />
<ClInclude Include="UnlDripGame.h" />
<ClInclude Include="UnlDripGameAudio.h" />
<ClInclude Include="UnlPuzzle.h" />
<ClInclude Include="UnlVrc7.h" />
<ClInclude Include="UnRom512.h" />

View file

@ -1432,6 +1432,12 @@
<ClInclude Include="BmcGn45.h">
<Filter>Nes\Mappers\Unif</Filter>
</ClInclude>
<ClInclude Include="UnlDripGame.h">
<Filter>Nes\Mappers\Unif</Filter>
</ClInclude>
<ClInclude Include="UnlDripGameAudio.h">
<Filter>Nes\Mappers\Unif</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="stdafx.cpp">

View file

@ -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();

View file

@ -70,5 +70,6 @@ namespace UnifBoards {
BmcHpxx,
DragonFighter,
BmcGn45,
UnlDripGame,
};
}

View file

@ -160,4 +160,5 @@ std::unordered_map<string, int> 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 },
};

169
Core/UnlDripGame.h Normal file
View file

@ -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<uint8_t> extAttributes1 { _extendedAttributes[0], 0x400 };
ArrayInfo<uint8_t> 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;
}
}
};

135
Core/UnlDripGameAudio.h Normal file
View file

@ -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<uint8_t> 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;
}
}
};