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