diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 29fd33122..f68fe3b0f 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -1093,6 +1093,7 @@ + diff --git a/Core/WS/Carts/WsCart.cpp b/Core/WS/Carts/WsCart.cpp index 15aef58bc..0872d4087 100644 --- a/Core/WS/Carts/WsCart.cpp +++ b/Core/WS/Carts/WsCart.cpp @@ -1,3 +1,4 @@ +#include "WS/Carts/WsKarnak.h" #include "pch.h" #include "WS/Carts/WsCart.h" #include "WS/WsMemoryManager.h" @@ -22,10 +23,12 @@ WsCart::WsCart() _state.SelectedBanks[3] = 0xFF; } -void WsCart::Init(WsMemoryManager* memoryManager, WsEeprom* cartEeprom) +void WsCart::Init(WsMemoryManager* memoryManager, WsEeprom* cartEeprom, WsKarnak* cartKarnak) { _memoryManager = memoryManager; _cartEeprom = cartEeprom; + _cartKarnak = cartKarnak; + _state.HasKarnak = cartKarnak != nullptr; } void WsCart::RefreshMappings() @@ -42,6 +45,8 @@ uint8_t WsCart::ReadPort(uint16_t port) return _state.SelectedBanks[port - 0xC0]; } else if(port < 0xC9 && _cartEeprom) { return _cartEeprom->ReadPort(port - 0xC4); + } else if(port >= 0xD6 && port < 0xDA && _cartKarnak) { + return _cartKarnak->ReadPort(port); } return _memoryManager->GetUnmappedPort(); @@ -54,6 +59,8 @@ void WsCart::WritePort(uint16_t port, uint8_t value) _memoryManager->RefreshMappings(); } else if(port < 0xC9 && _cartEeprom) { _cartEeprom->WritePort(port - 0xC4, value); + } else if(port >= 0xD6 && port < 0xDA && _cartKarnak) { + _cartKarnak->WritePort(port, value); } } diff --git a/Core/WS/Carts/WsCart.h b/Core/WS/Carts/WsCart.h index 87af797a9..4a5dd6147 100644 --- a/Core/WS/Carts/WsCart.h +++ b/Core/WS/Carts/WsCart.h @@ -17,6 +17,7 @@ class WsCart final : public ISerializable WsMemoryManager* _memoryManager = nullptr; WsEeprom* _cartEeprom = nullptr; + WsKarnak* _cartKarnak = nullptr; void Map(uint32_t start, uint32_t end, MemoryType type, uint32_t offset, bool readonly); void Unmap(uint32_t start, uint32_t end); @@ -25,7 +26,7 @@ class WsCart final : public ISerializable WsCart(); virtual ~WsCart() {} - void Init(WsMemoryManager* memoryManager, WsEeprom* cartEeprom); + void Init(WsMemoryManager* memoryManager, WsEeprom* cartEeprom, WsKarnak* cartKarnak); void RefreshMappings(); WsCartState& GetState() { return _state; } diff --git a/Core/WS/Carts/WsKarnak.cpp b/Core/WS/Carts/WsKarnak.cpp new file mode 100644 index 000000000..1f6ae12be --- /dev/null +++ b/Core/WS/Carts/WsKarnak.cpp @@ -0,0 +1,94 @@ +#include "pch.h" +#include "WS/Carts/WsKarnak.h" +#include "Utilities/Serializer.h" + +static const int16_t adpcmAccumulatorStep[16][16] = { + { 0, 0, 1, 2, 3, 5, 7, 10, 0, 0, -1, -2, -3, -5, -7, -10 }, + { 0, 1, 2, 3, 4, 6, 8, 13, 0, -1, -2, -3, -4, -6, -8, -13 }, + { 0, 1, 2, 4, 5, 7, 10, 15, 0, -1, -2, -4, -5, -7, -10, -15 }, + { 0, 1, 3, 4, 6, 9, 13, 19, 0, -1, -3, -4, -6, -9, -13, -19 }, + { 0, 2, 3, 5, 8, 11, 15, 23, 0, -2, -3, -5, -8, -11, -15, -23 }, + { 0, 2, 4, 7, 10, 14, 19, 29, 0, -2, -4, -7, -10, -14, -19, -29 }, + { 0, 3, 5, 8, 12, 16, 22, 33, 0, -3, -5, -8, -12, -16, -22, -33 }, + { 1, 4, 7, 10, 15, 20, 29, 43, -1, -4, -7, -10, -15, -20, -29, -43 }, + { 1, 4, 8, 13, 18, 25, 35, 53, -1, -4, -8, -13, -18, -25, -35, -53 }, + { 1, 6, 10, 16, 22, 31, 43, 64, -1, -6, -10, -16, -22, -31, -43, -64 }, + { 2, 7, 12, 19, 27, 37, 51, 76, -2, -7, -12, -19, -27, -37, -51, -76 }, + { 2, 9, 16, 24, 34, 46, 64, 96, -2, -9, -16, -24, -34, -46, -64, -96 }, + { 3, 11, 19, 29, 41, 57, 79, 117, -3, -11, -19, -29, -41, -57, -79, -117 }, + { 4, 13, 24, 36, 50, 69, 96, 143, -4, -13, -24, -36, -50, -69, -96, -143 }, + { 4, 16, 29, 44, 62, 85, 118, 175, -4, -16, -29, -44, -62, -85, -118, -175 }, + { 6, 20, 36, 54, 76, 104, 144, 214, -6, -20, -36, -54, -76, -104, -144, -214 } +}; +static const int8_t adpcmIndexStep[8] = { + -1, + -1, + 0, + 0, + 1, + 2, + 2, + 3 +}; + +WsKarnak::WsKarnak() +{ + Reset(); +} + +void WsKarnak::Reset() +{ + _state.AdpcmAccumulator = 0x100; + _adpcmInputShift = 0; + _adpcmStepIndex = 0; +} + +void WsKarnak::ProcessAdpcmInput(uint8_t sample) +{ + _state.AdpcmAccumulator += adpcmAccumulatorStep[_adpcmStepIndex][sample & 0xF]; + _state.AdpcmAccumulator &= 0x3FF; + _adpcmStepIndex = std::clamp(_adpcmStepIndex + adpcmIndexStep[sample & 0x7], 0, 15); +} + +uint8_t WsKarnak::ReadPort(uint16_t port) +{ + if(port == 0xD6) { + return (_state.Enable ? 0x80 : 0) | (_state.TimerPeriod & 0x7F); + } else if(port == 0xD9) { + if(_state.AdpcmAccumulator >= 0x300) { + return 0x00; + } else if(_state.AdpcmAccumulator >= 0x200) { + return 0xFF; + } else { + return _state.AdpcmAccumulator >> 1; + } + } + + return 0xFF; +} + +void WsKarnak::WritePort(uint16_t port, uint8_t value) +{ + if(port == 0xD6) { + _state.Enable = (value & 0x80) != 0; + _state.TimerPeriod = value & 0x7F; + if(!_state.Enable) { + Reset(); + } + } else if(port == 0xD8) { + if(_state.Enable) { + ProcessAdpcmInput(value >> (_adpcmInputShift ? 0 : 4)); + _adpcmInputShift ^= 1; + } + } +} + +void WsKarnak::Serialize(Serializer& s) +{ + SV(_state.Enable); + SV(_state.TimerPeriod); + SV(_state.AdpcmAccumulator); + SV(_adpcmInputShift); + SV(_adpcmStepIndex); + SV(_timerCounter); +} diff --git a/Core/WS/Carts/WsKarnak.h b/Core/WS/Carts/WsKarnak.h new file mode 100644 index 000000000..d2ecf772f --- /dev/null +++ b/Core/WS/Carts/WsKarnak.h @@ -0,0 +1,28 @@ +#pragma once +#include "pch.h" +#include "WS/WsTypes.h" +#include "Utilities/ISerializable.h" + +class WsKarnak final : public ISerializable +{ +protected: + WsKarnakState _state = {}; + + uint16_t _timerCounter; + int8_t _adpcmStepIndex; + uint8_t _adpcmInputShift; + + void Reset(); + void ProcessAdpcmInput(uint8_t sample); + +public: + WsKarnak(); + virtual ~WsKarnak() {} + + WsKarnakState& GetState() { return _state; } + + virtual uint8_t ReadPort(uint16_t port); + virtual void WritePort(uint16_t port, uint8_t value); + + void Serialize(Serializer& s) override; +}; diff --git a/Core/WS/WsConsole.cpp b/Core/WS/WsConsole.cpp index b6445985b..8e3d98b82 100644 --- a/Core/WS/WsConsole.cpp +++ b/Core/WS/WsConsole.cpp @@ -4,6 +4,7 @@ #include "WS/WsPpu.h" #include "WS/WsTimer.h" #include "WS/Carts/WsCart.h" +#include "WS/Carts/WsKarnak.h" #include "WS/WsControlManager.h" #include "WS/WsMemoryManager.h" #include "WS/WsDmaController.h" @@ -128,8 +129,9 @@ LoadRomResult WsConsole::LoadRom(VirtualFile& romFile) _ppu.reset(new WsPpu(_emu, this, _memoryManager.get(), _timer.get(), _workRam)); _apu.reset(new WsApu(_emu, this, _memoryManager.get(), _dmaController.get())); _cart.reset(new WsCart()); + _cartKarnak.reset(new WsKarnak()); - _cart->Init(_memoryManager.get(), _cartEeprom.get()); + _cart->Init(_memoryManager.get(), _cartEeprom.get(), _cartKarnak.get()); _memoryManager->Init(_emu, this, _cpu.get(), _ppu.get(), _controlManager.get(), _cart.get(), _timer.get(), _dmaController.get(), _internalEeprom.get(), _apu.get(), _serial.get()); _timer->Init(_memoryManager.get()); _dmaController->Init(_memoryManager.get(), _apu.get()); @@ -447,6 +449,9 @@ WsState WsConsole::GetState() if(_cartEeprom) { state.CartEeprom = _cartEeprom->GetState(); } + if(_cartKarnak) { + state.CartKarnak = _cartKarnak->GetState(); + } return state; } @@ -496,4 +501,7 @@ void WsConsole::Serialize(Serializer& s) if(_cartEeprom) { SV(_cartEeprom); } + if(_cartKarnak) { + SV(_cartKarnak); + } } diff --git a/Core/WS/WsConsole.h b/Core/WS/WsConsole.h index 628016f73..e9f755807 100644 --- a/Core/WS/WsConsole.h +++ b/Core/WS/WsConsole.h @@ -14,6 +14,7 @@ class WsMemoryManager; class WsControlManager; class WsDmaController; class WsEeprom; +class WsKarnak; enum class WsModel : uint8_t; class WsConsole final : public IConsole @@ -31,6 +32,7 @@ class WsConsole final : public IConsole unique_ptr _dmaController; unique_ptr _internalEeprom; unique_ptr _cartEeprom; + unique_ptr _cartKarnak; uint8_t* _workRam = nullptr; uint32_t _workRamSize = 0; diff --git a/Core/WS/WsTypes.h b/Core/WS/WsTypes.h index f7c80fa90..327b5e21b 100644 --- a/Core/WS/WsTypes.h +++ b/Core/WS/WsTypes.h @@ -445,9 +445,17 @@ struct WsEepromState struct WsCartState { + bool HasKarnak; uint8_t SelectedBanks[4]; }; +struct WsKarnakState +{ + bool Enable; + uint8_t TimerPeriod; + uint16_t AdpcmAccumulator; +}; + struct WsState { WsCpuState Cpu; @@ -461,6 +469,7 @@ struct WsState WsEepromState InternalEeprom; WsCartState Cart; WsEepromState CartEeprom; + WsKarnakState CartKarnak; WsModel Model; }; diff --git a/UI/Debugger/RegisterViewer/WsRegisterViewer.cs b/UI/Debugger/RegisterViewer/WsRegisterViewer.cs index 40ac369a9..b0178412d 100644 --- a/UI/Debugger/RegisterViewer/WsRegisterViewer.cs +++ b/UI/Debugger/RegisterViewer/WsRegisterViewer.cs @@ -383,6 +383,15 @@ private static RegisterViewerTab GetCartTab(ref WsState ws) }); } + if(ws.Cart.HasKarnak) { + entries.AddRange(new List() { + new RegEntry("", "Cart Karnak"), + new RegEntry("$D6.7", "Enabled", ws.CartKarnak.Enable), + new RegEntry("$D6.0-6", "Timer Period", ws.CartKarnak.TimerPeriod, Format.X8), + new RegEntry("$D9", "ADPCM Output", ws.CartKarnak.AdpcmAccumulator >= 0x300 ? 0x00 : (ws.CartKarnak.AdpcmAccumulator >= 0x200 ? 0xFF : (ws.CartKarnak.AdpcmAccumulator >> 1)), Format.X8) + }); + } + return new RegisterViewerTab("Cart", entries, CpuType.Ws, MemoryType.WsPort); } diff --git a/UI/Interop/ConsoleState/WsState.cs b/UI/Interop/ConsoleState/WsState.cs index af8d317cc..dbdf5b528 100644 --- a/UI/Interop/ConsoleState/WsState.cs +++ b/UI/Interop/ConsoleState/WsState.cs @@ -442,10 +442,18 @@ public struct WsEepromState public struct WsCartState { + [MarshalAs(UnmanagedType.I1)] public bool HasKarnak; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] public byte[] SelectedBanks; } +public struct WsKarnakState +{ + [MarshalAs(UnmanagedType.I1)] public bool Enable; + public byte TimerPeriod; + public UInt16 AdpcmAccumulator; +} + public struct WsState : BaseState { public WsCpuState Cpu; @@ -459,5 +467,6 @@ public struct WsState : BaseState public WsEepromState InternalEeprom; public WsCartState Cart; public WsEepromState CartEeprom; + public WsKarnakState CartKarnak; public WsModel Model; }