mirror of
https://github.com/SourMesen/Mesen2.git
synced 2026-10-12 02:09:42 +00:00
Added WonderSwan support
This commit is contained in:
@@ -754,12 +754,40 @@
|
||||
<ClInclude Include="Shared\Video\VideoRenderer.h" />
|
||||
<ClInclude Include="Shared\Audio\WaveRecorder.h" />
|
||||
<ClInclude Include="Shared\Interfaces\IMouseManager.h" />
|
||||
<ClInclude Include="WS\APU\WsApuCh1.h" />
|
||||
<ClInclude Include="WS\APU\WsApuCh2.h" />
|
||||
<ClInclude Include="WS\APU\WsApuCh3.h" />
|
||||
<ClInclude Include="WS\APU\WsApuCh4.h" />
|
||||
<ClInclude Include="WS\APU\WsHyperVoice.h" />
|
||||
<ClInclude Include="WS\Carts\WsCart.h" />
|
||||
<ClInclude Include="WS\Debugger\DummyWsCpu.h" />
|
||||
<ClInclude Include="WS\Debugger\WsAssembler.h" />
|
||||
<ClInclude Include="WS\Debugger\WsDebugger.h" />
|
||||
<ClInclude Include="WS\Debugger\WsDisUtils.h" />
|
||||
<ClInclude Include="WS\Debugger\WsEventManager.h" />
|
||||
<ClInclude Include="WS\Debugger\WsPpuTools.h" />
|
||||
<ClInclude Include="WS\Debugger\WsTraceLogger.h" />
|
||||
<ClInclude Include="WS\APU\WsApu.h" />
|
||||
<ClInclude Include="WS\WsConsole.h" />
|
||||
<ClInclude Include="WS\WsController.h" />
|
||||
<ClInclude Include="WS\WsControlManager.h" />
|
||||
<ClInclude Include="WS\WsCpu.h" />
|
||||
<ClInclude Include="WS\WsCpuPrefetch.h" />
|
||||
<ClInclude Include="WS\WsDefaultVideoFilter.h" />
|
||||
<ClInclude Include="WS\WsDmaController.h" />
|
||||
<ClInclude Include="WS\WsEeprom.h" />
|
||||
<ClInclude Include="WS\WsMemoryManager.h" />
|
||||
<ClInclude Include="WS\WsPpu.h" />
|
||||
<ClInclude Include="WS\WsSerial.h" />
|
||||
<ClInclude Include="WS\WsTimer.h" />
|
||||
<ClInclude Include="WS\WsTypes.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="Debugger\Base6502Assembler.cpp" />
|
||||
<ClCompile Include="Debugger\BaseEventManager.cpp" />
|
||||
<ClCompile Include="Debugger\CdlManager.cpp" />
|
||||
<ClCompile Include="Debugger\DisassemblySearch.cpp" />
|
||||
<ClCompile Include="Debugger\ExpressionEvaluator.Ws.cpp" />
|
||||
<ClCompile Include="Debugger\ExpressionEvaluator.Cx4.cpp" />
|
||||
<ClCompile Include="Debugger\ExpressionEvaluator.Gameboy.cpp" />
|
||||
<ClCompile Include="Debugger\ExpressionEvaluator.Gba.cpp" />
|
||||
@@ -1032,6 +1060,25 @@
|
||||
<ClCompile Include="Shared\Video\VideoDecoder.cpp" />
|
||||
<ClCompile Include="Shared\Video\VideoRenderer.cpp" />
|
||||
<ClCompile Include="Shared\Audio\WaveRecorder.cpp" />
|
||||
<ClCompile Include="WS\APU\WsApu.cpp" />
|
||||
<ClCompile Include="WS\Carts\WsCart.cpp" />
|
||||
<ClCompile Include="WS\Debugger\DummyWsCpu.cpp" />
|
||||
<ClCompile Include="WS\Debugger\WsDebugger.cpp" />
|
||||
<ClCompile Include="WS\Debugger\WsDisUtils.cpp" />
|
||||
<ClCompile Include="WS\Debugger\WsEventManager.cpp" />
|
||||
<ClCompile Include="WS\Debugger\WsPpuTools.cpp" />
|
||||
<ClCompile Include="WS\Debugger\WsTraceLogger.cpp" />
|
||||
<ClCompile Include="WS\WsConsole.cpp" />
|
||||
<ClCompile Include="WS\WsControlManager.cpp" />
|
||||
<ClCompile Include="WS\WsCpu.cpp" />
|
||||
<ClCompile Include="WS\WsCpuPrefetch.cpp" />
|
||||
<ClCompile Include="WS\WsDefaultVideoFilter.cpp" />
|
||||
<ClCompile Include="WS\WsDmaController.cpp" />
|
||||
<ClCompile Include="WS\WsEeprom.cpp" />
|
||||
<ClCompile Include="WS\WsMemoryManager.cpp" />
|
||||
<ClCompile Include="WS\WsPpu.cpp" />
|
||||
<ClCompile Include="WS\WsSerial.cpp" />
|
||||
<ClCompile Include="WS\WsTimer.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="Core.ruleset" />
|
||||
|
||||
@@ -2841,6 +2841,87 @@
|
||||
<ClInclude Include="SMS\Input\ColecoVisionController.h">
|
||||
<Filter>SMS\Input</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WS\WsTypes.h">
|
||||
<Filter>WS</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WS\WsCpu.h">
|
||||
<Filter>WS</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WS\WsConsole.h">
|
||||
<Filter>WS</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WS\WsMemoryManager.h">
|
||||
<Filter>WS</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WS\WsControlManager.h">
|
||||
<Filter>WS</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WS\WsDefaultVideoFilter.h">
|
||||
<Filter>WS</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WS\Debugger\WsDebugger.h">
|
||||
<Filter>WS\Debugger</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WS\Debugger\WsAssembler.h">
|
||||
<Filter>WS\Debugger</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WS\Debugger\WsDisUtils.h">
|
||||
<Filter>WS\Debugger</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WS\Debugger\WsEventManager.h">
|
||||
<Filter>WS\Debugger</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WS\Debugger\WsPpuTools.h">
|
||||
<Filter>WS\Debugger</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WS\Debugger\WsTraceLogger.h">
|
||||
<Filter>WS\Debugger</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WS\WsPpu.h">
|
||||
<Filter>WS</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WS\WsController.h">
|
||||
<Filter>WS</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WS\Carts\WsCart.h">
|
||||
<Filter>WS\Carts</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WS\WsTimer.h">
|
||||
<Filter>WS</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WS\WsDmaController.h">
|
||||
<Filter>WS</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WS\WsEeprom.h">
|
||||
<Filter>WS</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WS\WsCpuPrefetch.h">
|
||||
<Filter>WS</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WS\APU\WsApuCh1.h">
|
||||
<Filter>WS\APU</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WS\APU\WsApuCh2.h">
|
||||
<Filter>WS\APU</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WS\APU\WsApuCh3.h">
|
||||
<Filter>WS\APU</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WS\APU\WsApuCh4.h">
|
||||
<Filter>WS\APU</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WS\APU\WsApu.h">
|
||||
<Filter>WS\APU</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WS\APU\WsHyperVoice.h">
|
||||
<Filter>WS\APU</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WS\Debugger\DummyWsCpu.h">
|
||||
<Filter>WS\Debugger</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WS\WsSerial.h">
|
||||
<Filter>WS</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="Shared\Video\RotateFilter.cpp">
|
||||
@@ -3116,6 +3197,66 @@
|
||||
<ClCompile Include="GBA\Cart\GbaGpio.cpp">
|
||||
<Filter>GBA\Cart</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="WS\WsConsole.cpp">
|
||||
<Filter>WS</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="WS\Debugger\WsDebugger.cpp">
|
||||
<Filter>WS\Debugger</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="WS\Debugger\WsDisUtils.cpp">
|
||||
<Filter>WS\Debugger</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="WS\Debugger\WsEventManager.cpp">
|
||||
<Filter>WS\Debugger</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="WS\Debugger\WsPpuTools.cpp">
|
||||
<Filter>WS\Debugger</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="WS\Debugger\WsTraceLogger.cpp">
|
||||
<Filter>WS\Debugger</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="WS\WsCpu.cpp">
|
||||
<Filter>WS</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Debugger\ExpressionEvaluator.Ws.cpp">
|
||||
<Filter>Debugger</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="WS\WsControlManager.cpp">
|
||||
<Filter>WS</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="WS\WsPpu.cpp">
|
||||
<Filter>WS</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="WS\WsMemoryManager.cpp">
|
||||
<Filter>WS</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="WS\Carts\WsCart.cpp">
|
||||
<Filter>WS\Carts</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="WS\WsTimer.cpp">
|
||||
<Filter>WS</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="WS\WsDmaController.cpp">
|
||||
<Filter>WS</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="WS\WsCpuPrefetch.cpp">
|
||||
<Filter>WS</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="WS\APU\WsApu.cpp">
|
||||
<Filter>WS\APU</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="WS\Debugger\DummyWsCpu.cpp">
|
||||
<Filter>WS\Debugger</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="WS\WsSerial.cpp">
|
||||
<Filter>WS</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="WS\WsDefaultVideoFilter.cpp">
|
||||
<Filter>WS</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="WS\WsEeprom.cpp">
|
||||
<Filter>WS</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Filter Include="PCE">
|
||||
@@ -3226,5 +3367,17 @@
|
||||
<Filter Include="GBA\Cart">
|
||||
<UniqueIdentifier>{4cc96bee-c354-4a9b-9947-85959ccc072f}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="WS">
|
||||
<UniqueIdentifier>{8d532045-df57-4c7d-be0b-19680c2c2d10}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="WS\Debugger">
|
||||
<UniqueIdentifier>{14eeda13-2b16-4034-9006-7a7a347f548d}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="WS\Carts">
|
||||
<UniqueIdentifier>{ffbcd5be-271d-4c27-81f4-7684bfb2b8d9}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="WS\APU">
|
||||
<UniqueIdentifier>{e8ed2b40-f0e0-4dc5-89b7-9acc460359c8}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -107,6 +107,19 @@ enum class RowDataType
|
||||
|
||||
CPSR,
|
||||
Mode,
|
||||
|
||||
AX,
|
||||
BX,
|
||||
CX,
|
||||
DX,
|
||||
CS,
|
||||
IP,
|
||||
SS,
|
||||
BP,
|
||||
DS,
|
||||
ES,
|
||||
SI,
|
||||
DI
|
||||
};
|
||||
|
||||
struct TraceLogPpuState
|
||||
@@ -256,7 +269,7 @@ protected:
|
||||
template<typename T>
|
||||
void WriteIntValue(string& output, T value, RowPart& rowPart)
|
||||
{
|
||||
string str = rowPart.DisplayInHex ? HexUtilities::ToHex(value) : std::to_string(value);
|
||||
string str = rowPart.DisplayInHex ? DebugUtilities::AddressToHex(_cpuType, value) : std::to_string(value);
|
||||
if(rowPart.MinWidth > (int)str.size()) {
|
||||
if(rowPart.DisplayInHex) {
|
||||
str = std::string(rowPart.MinWidth - str.size(), '0') + str;
|
||||
|
||||
@@ -201,6 +201,7 @@ enum class TileFormat
|
||||
SmsSgBpp1,
|
||||
GbaBpp4,
|
||||
GbaBpp8,
|
||||
WsBpp4Packed
|
||||
};
|
||||
|
||||
enum class TileLayout
|
||||
@@ -332,7 +333,9 @@ enum class BreakSource
|
||||
|
||||
GbaInvalidOpCode,
|
||||
GbaNopLoad,
|
||||
GbaUnalignedMemoryAccess
|
||||
GbaUnalignedMemoryAccess,
|
||||
|
||||
BreakOnUndefinedOpCode
|
||||
};
|
||||
|
||||
struct BreakEvent
|
||||
@@ -476,6 +479,8 @@ struct DebugControllerState
|
||||
bool Y;
|
||||
bool L;
|
||||
bool R;
|
||||
bool U;
|
||||
bool D;
|
||||
bool Up;
|
||||
bool Down;
|
||||
bool Left;
|
||||
@@ -485,6 +490,6 @@ struct DebugControllerState
|
||||
|
||||
bool HasPressedButton()
|
||||
{
|
||||
return A || B || X || Y || L || R || Up || Down || Left || Right || Select || Start;
|
||||
return A || B || X || Y || L || R || U || D || Up || Down || Left || Right || Select || Start;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -21,6 +21,7 @@ public:
|
||||
case CpuType::Pce: return MemoryType::PceMemory;
|
||||
case CpuType::Sms: return MemoryType::SmsMemory;
|
||||
case CpuType::Gba: return MemoryType::GbaMemory;
|
||||
case CpuType::Ws: return MemoryType::WsMemory;
|
||||
}
|
||||
|
||||
throw std::runtime_error("Invalid CPU type");
|
||||
@@ -40,6 +41,7 @@ public:
|
||||
case CpuType::Pce: return 4;
|
||||
case CpuType::Sms: return 4;
|
||||
case CpuType::Gba: return 8;
|
||||
case CpuType::Ws: return 5;
|
||||
}
|
||||
|
||||
throw std::runtime_error("Invalid CPU type");
|
||||
@@ -144,6 +146,16 @@ public:
|
||||
case MemoryType::GbaPaletteRam:
|
||||
return CpuType::Gba;
|
||||
|
||||
case MemoryType::WsMemory:
|
||||
case MemoryType::WsPrgRom:
|
||||
case MemoryType::WsWorkRam:
|
||||
case MemoryType::WsCartRam:
|
||||
case MemoryType::WsCartEeprom:
|
||||
case MemoryType::WsBootRom:
|
||||
case MemoryType::WsInternalEeprom:
|
||||
case MemoryType::WsPort:
|
||||
return CpuType::Ws;
|
||||
|
||||
default:
|
||||
throw std::runtime_error("Invalid CPU type");
|
||||
}
|
||||
@@ -156,7 +168,7 @@ public:
|
||||
|
||||
static constexpr MemoryType GetLastCpuMemoryType()
|
||||
{
|
||||
return MemoryType::GbaMemory;
|
||||
return MemoryType::WsMemory;
|
||||
}
|
||||
|
||||
static constexpr bool IsPpuMemory(MemoryType memType)
|
||||
@@ -214,6 +226,7 @@ public:
|
||||
case MemoryType::SmsBootRom:
|
||||
case MemoryType::GbaPrgRom:
|
||||
case MemoryType::GbaBootRom:
|
||||
case MemoryType::WsPrgRom:
|
||||
return true;
|
||||
|
||||
default:
|
||||
@@ -235,6 +248,7 @@ public:
|
||||
case MemoryType::SnesRegister:
|
||||
case MemoryType::SmsCartRam:
|
||||
case MemoryType::GbaSaveRam:
|
||||
case MemoryType::WsCartRam:
|
||||
return false;
|
||||
|
||||
default:
|
||||
@@ -244,7 +258,7 @@ public:
|
||||
|
||||
static constexpr CpuType GetLastCpuType()
|
||||
{
|
||||
return CpuType::Gba;
|
||||
return CpuType::Ws;
|
||||
}
|
||||
|
||||
static string AddressToHex(CpuType cpuType, int32_t address)
|
||||
@@ -252,8 +266,12 @@ public:
|
||||
int size = GetProgramCounterSize(cpuType);
|
||||
if(size == 4) {
|
||||
return HexUtilities::ToHex((uint16_t)address);
|
||||
} else if(size == 5) {
|
||||
return HexUtilities::ToHex20(address);
|
||||
} else if(size == 6) {
|
||||
return HexUtilities::ToHex24(address);
|
||||
} else if(size == 8) {
|
||||
return HexUtilities::ToHex32(address);
|
||||
} else {
|
||||
return HexUtilities::ToHex(address);
|
||||
}
|
||||
|
||||
@@ -40,6 +40,8 @@
|
||||
#include "SMS/SmsTypes.h"
|
||||
#include "GBA/Debugger/GbaDebugger.h"
|
||||
#include "GBA/GbaTypes.h"
|
||||
#include "WS/Debugger/WsDebugger.h"
|
||||
#include "WS/WsTypes.h"
|
||||
#include "Shared/BaseControlManager.h"
|
||||
#include "Shared/EmuSettings.h"
|
||||
#include "Shared/Audio/SoundMixer.h"
|
||||
@@ -93,6 +95,7 @@ Debugger::Debugger(Emulator* emu, IConsole* console)
|
||||
case CpuType::Pce: debugger.reset(new PceDebugger(this)); break;
|
||||
case CpuType::Sms: debugger.reset(new SmsDebugger(this)); break;
|
||||
case CpuType::Gba: debugger.reset(new GbaDebugger(this)); break;
|
||||
case CpuType::Ws: debugger.reset(new WsDebugger(this)); break;
|
||||
default: throw std::runtime_error("Unsupported CPU type");
|
||||
}
|
||||
|
||||
@@ -172,6 +175,7 @@ uint64_t Debugger::GetCpuCycleCount()
|
||||
case CpuType::Pce: return GetDebugger<type, PceDebugger>()->GetCpuCycleCount();
|
||||
case CpuType::Sms: return GetDebugger<type, SmsDebugger>()->GetCpuCycleCount();
|
||||
case CpuType::Gba: return GetDebugger<type, GbaDebugger>()->GetCpuCycleCount();
|
||||
case CpuType::Ws: return GetDebugger<type, WsDebugger>()->GetCpuCycleCount();
|
||||
default: return 0; break;
|
||||
}
|
||||
}
|
||||
@@ -218,6 +222,7 @@ void Debugger::ProcessInstruction()
|
||||
case CpuType::Pce: GetDebugger<type, PceDebugger>()->ProcessInstruction(); break;
|
||||
case CpuType::Sms: GetDebugger<type, SmsDebugger>()->ProcessInstruction(); break;
|
||||
case CpuType::Gba: GetDebugger<type, GbaDebugger>()->ProcessInstruction(); break;
|
||||
case CpuType::Ws: GetDebugger<type, WsDebugger>()->ProcessInstruction(); break;
|
||||
}
|
||||
|
||||
debugger->AllowChangeProgramCounter = false;
|
||||
@@ -250,6 +255,11 @@ void Debugger::ProcessMemoryRead(uint32_t addr, T& value, MemoryOperationType op
|
||||
case CpuType::Pce: GetDebugger<CpuType::Pce, PceDebugger>()->ProcessRead(addr, value, opType); break;
|
||||
case CpuType::Sms: GetDebugger<CpuType::Sms, SmsDebugger>()->ProcessRead(addr, value, opType); break;
|
||||
case CpuType::Gba: GetDebugger<CpuType::Gba, GbaDebugger>()->ProcessRead<accessWidth>(addr, value, opType); break;
|
||||
case CpuType::Ws:
|
||||
if constexpr(accessWidth <= 2) {
|
||||
GetDebugger<CpuType::Ws, WsDebugger>()->ProcessRead<accessWidth>(addr, value, opType);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if(_scriptManager->HasCpuMemoryCallbacks()) {
|
||||
@@ -277,6 +287,11 @@ bool Debugger::ProcessMemoryWrite(uint32_t addr, T& value, MemoryOperationType o
|
||||
case CpuType::Pce: GetDebugger<CpuType::Pce, PceDebugger>()->ProcessWrite(addr, value, opType); break;
|
||||
case CpuType::Sms: GetDebugger<CpuType::Sms, SmsDebugger>()->ProcessWrite(addr, value, opType); break;
|
||||
case CpuType::Gba: GetDebugger<CpuType::Gba, GbaDebugger>()->ProcessWrite<accessWidth>(addr, value, opType); break;
|
||||
case CpuType::Ws:
|
||||
if constexpr(accessWidth <= 2) {
|
||||
GetDebugger<CpuType::Ws, WsDebugger>()->ProcessWrite<accessWidth>(addr, value, opType);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if(_scriptManager->HasCpuMemoryCallbacks()) {
|
||||
@@ -291,6 +306,8 @@ void Debugger::ProcessMemoryAccess(uint32_t addr, T& value)
|
||||
{
|
||||
IDebugger* debugger = _debuggers[(int)cpuType].Debugger.get();
|
||||
|
||||
constexpr int accessWidth = std::is_same<T, uint16_t>::value ? 2 : 1;
|
||||
|
||||
if(debugger->IsStepBack()) {
|
||||
return;
|
||||
}
|
||||
@@ -299,17 +316,22 @@ void Debugger::ProcessMemoryAccess(uint32_t addr, T& value)
|
||||
MemoryOperationInfo operation(addr, value, opType, memType);
|
||||
|
||||
if constexpr(opType == MemoryOperationType::Write) {
|
||||
_memoryAccessCounter->ProcessMemoryWrite(addressInfo, _emu->GetMasterClock());
|
||||
_memoryAccessCounter->ProcessMemoryWrite<accessWidth>(addressInfo, _emu->GetMasterClock());
|
||||
} else {
|
||||
_memoryAccessCounter->ProcessMemoryRead(addressInfo, _emu->GetMasterClock());
|
||||
_memoryAccessCounter->ProcessMemoryRead<accessWidth>(addressInfo, _emu->GetMasterClock());
|
||||
}
|
||||
|
||||
switch(cpuType) {
|
||||
default: break;
|
||||
case CpuType::Sms: GetDebugger<CpuType::Sms, SmsDebugger>()->ProcessMemoryAccess<opType>(addr, value, memType); break;
|
||||
case CpuType::Ws: GetDebugger<CpuType::Ws, WsDebugger>()->ProcessMemoryAccess<opType, T>(addr, value, memType); break;
|
||||
}
|
||||
|
||||
ProcessBreakConditions(cpuType, *debugger->GetStepRequest(), debugger->GetBreakpointManager(), operation, addressInfo);
|
||||
if(_scriptManager->HasCpuMemoryCallbacks()) {
|
||||
ProcessScripts<cpuType>(addr, value, memType, opType);
|
||||
}
|
||||
|
||||
ProcessBreakConditions<accessWidth>(cpuType, *debugger->GetStepRequest(), debugger->GetBreakpointManager(), operation, addressInfo);
|
||||
}
|
||||
|
||||
template<CpuType type>
|
||||
@@ -420,6 +442,7 @@ void Debugger::ProcessPpuCycle()
|
||||
case CpuType::Pce: GetDebugger<type, PceDebugger>()->ProcessPpuCycle(); break;
|
||||
case CpuType::Sms: GetDebugger<type, SmsDebugger>()->ProcessPpuCycle(); break;
|
||||
case CpuType::Gba: GetDebugger<type, GbaDebugger>()->ProcessPpuCycle(); break;
|
||||
case CpuType::Ws: GetDebugger<type, WsDebugger>()->ProcessPpuCycle(); break;
|
||||
default: throw std::runtime_error("Invalid cpu type");
|
||||
}
|
||||
}
|
||||
@@ -627,6 +650,7 @@ void Debugger::PauseOnNextFrame()
|
||||
case CpuType::Pce: Step(CpuType::Pce, 243, StepType::SpecificScanline, BreakSource::PpuStep); break;
|
||||
case CpuType::Sms: Step(CpuType::Sms, 240, StepType::SpecificScanline, BreakSource::PpuStep); break;
|
||||
case CpuType::Gba: Step(CpuType::Gba, 160, StepType::SpecificScanline, BreakSource::PpuStep); break;
|
||||
case CpuType::Ws: Step(CpuType::Ws, 145, StepType::SpecificScanline, BreakSource::PpuStep); break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -714,6 +738,7 @@ bool Debugger::IsDebugWindowOpened(CpuType cpuType)
|
||||
case CpuType::Pce: return _settings->CheckDebuggerFlag(DebuggerFlags::PceDebuggerEnabled);
|
||||
case CpuType::Sms: return _settings->CheckDebuggerFlag(DebuggerFlags::SmsDebuggerEnabled);
|
||||
case CpuType::Gba: return _settings->CheckDebuggerFlag(DebuggerFlags::GbaDebuggerEnabled);
|
||||
case CpuType::Ws: return _settings->CheckDebuggerFlag(DebuggerFlags::WsDebuggerEnabled);
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -764,6 +789,7 @@ void Debugger::GetCpuState(BaseState &dstState, CpuType cpuType)
|
||||
case CpuType::Pce: memcpy(&dstState, &srcState, sizeof(PceCpuState)); break;
|
||||
case CpuType::Sms: memcpy(&dstState, &srcState, sizeof(SmsCpuState)); break;
|
||||
case CpuType::Gba: memcpy(&dstState, &srcState, sizeof(GbaCpuState)); break;
|
||||
case CpuType::Ws: memcpy(&dstState, &srcState, sizeof(WsCpuState)); break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -783,6 +809,7 @@ void Debugger::SetCpuState(BaseState& srcState, CpuType cpuType)
|
||||
case CpuType::Pce: memcpy(&dstState, &srcState, sizeof(PceCpuState)); break;
|
||||
case CpuType::Sms: memcpy(&dstState, &srcState, sizeof(SmsCpuState)); break;
|
||||
case CpuType::Gba: memcpy(&dstState, &srcState, sizeof(GbaCpuState)); break;
|
||||
case CpuType::Ws: memcpy(&dstState, &srcState, sizeof(WsCpuState)); break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -828,6 +855,11 @@ void Debugger::GetPpuState(BaseState& state, CpuType cpuType)
|
||||
GetDebugger<CpuType::Gba, GbaDebugger>()->GetPpuState(state);
|
||||
break;
|
||||
}
|
||||
|
||||
case CpuType::Ws: {
|
||||
GetDebugger<CpuType::Ws, WsDebugger>()->GetPpuState(state);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -869,6 +901,11 @@ void Debugger::SetPpuState(BaseState& state, CpuType cpuType)
|
||||
GetDebugger<CpuType::Gba, GbaDebugger>()->SetPpuState(state);
|
||||
break;
|
||||
}
|
||||
|
||||
case CpuType::Ws: {
|
||||
GetDebugger<CpuType::Ws, WsDebugger>()->SetPpuState(state);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -982,6 +1019,7 @@ bool Debugger::SaveRomToDisk(string filename, bool saveAsIps, CdlStripOption str
|
||||
case CpuType::Pce: return GetDebugger<CpuType::Pce, PceDebugger>()->SaveRomToDisk(filename, saveAsIps, stripOption);
|
||||
case CpuType::Sms: return GetDebugger<CpuType::Sms, SmsDebugger>()->SaveRomToDisk(filename, saveAsIps, stripOption);
|
||||
case CpuType::Gba: return GetDebugger<CpuType::Gba, GbaDebugger>()->SaveRomToDisk(filename, saveAsIps, stripOption);
|
||||
case CpuType::Ws: return GetDebugger<CpuType::Ws, WsDebugger>()->SaveRomToDisk(filename, saveAsIps, stripOption);
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -1100,6 +1138,7 @@ template void Debugger::ProcessInstruction<CpuType::Nes>();
|
||||
template void Debugger::ProcessInstruction<CpuType::Pce>();
|
||||
template void Debugger::ProcessInstruction<CpuType::Sms>();
|
||||
template void Debugger::ProcessInstruction<CpuType::Gba>();
|
||||
template void Debugger::ProcessInstruction<CpuType::Ws>();
|
||||
|
||||
template void Debugger::ProcessMemoryRead<CpuType::Snes>(uint32_t addr, uint8_t& value, MemoryOperationType opType);
|
||||
template void Debugger::ProcessMemoryRead<CpuType::Sa1>(uint32_t addr, uint8_t& value, MemoryOperationType opType);
|
||||
@@ -1116,6 +1155,8 @@ template void Debugger::ProcessMemoryRead<CpuType::Sms>(uint32_t addr, uint8_t&
|
||||
template void Debugger::ProcessMemoryRead<CpuType::Gba, 1>(uint32_t addr, uint32_t& value, MemoryOperationType opType);
|
||||
template void Debugger::ProcessMemoryRead<CpuType::Gba, 2>(uint32_t addr, uint32_t& value, MemoryOperationType opType);
|
||||
template void Debugger::ProcessMemoryRead<CpuType::Gba, 4>(uint32_t addr, uint32_t& value, MemoryOperationType opType);
|
||||
template void Debugger::ProcessMemoryRead<CpuType::Ws, 1>(uint32_t addr, uint8_t& value, MemoryOperationType opType);
|
||||
template void Debugger::ProcessMemoryRead<CpuType::Ws, 2>(uint32_t addr, uint16_t& value, MemoryOperationType opType);
|
||||
|
||||
template bool Debugger::ProcessMemoryWrite<CpuType::Snes>(uint32_t addr, uint8_t& value, MemoryOperationType opType);
|
||||
template bool Debugger::ProcessMemoryWrite<CpuType::Sa1>(uint32_t addr, uint8_t& value, MemoryOperationType opType);
|
||||
@@ -1131,6 +1172,8 @@ template bool Debugger::ProcessMemoryWrite<CpuType::Sms>(uint32_t addr, uint8_t&
|
||||
template bool Debugger::ProcessMemoryWrite<CpuType::Gba, 1>(uint32_t addr, uint32_t& value, MemoryOperationType opType);
|
||||
template bool Debugger::ProcessMemoryWrite<CpuType::Gba, 2>(uint32_t addr, uint32_t& value, MemoryOperationType opType);
|
||||
template bool Debugger::ProcessMemoryWrite<CpuType::Gba, 4>(uint32_t addr, uint32_t& value, MemoryOperationType opType);
|
||||
template bool Debugger::ProcessMemoryWrite<CpuType::Ws, 1>(uint32_t addr, uint8_t& value, MemoryOperationType opType);
|
||||
template bool Debugger::ProcessMemoryWrite<CpuType::Ws, 2>(uint32_t addr, uint16_t& value, MemoryOperationType opType);
|
||||
|
||||
template void Debugger::ProcessMemoryAccess<CpuType::Pce, MemoryType::PceAdpcmRam, MemoryOperationType::Write>(uint32_t addr, uint8_t& value);
|
||||
template void Debugger::ProcessMemoryAccess<CpuType::Pce, MemoryType::PceAdpcmRam, MemoryOperationType::Read>(uint32_t addr, uint8_t& value);
|
||||
@@ -1139,6 +1182,15 @@ template void Debugger::ProcessMemoryAccess<CpuType::Pce, MemoryType::PceArcadeC
|
||||
template void Debugger::ProcessMemoryAccess<CpuType::Sms, MemoryType::SmsPort, MemoryOperationType::Write>(uint32_t addr, uint8_t& value);
|
||||
template void Debugger::ProcessMemoryAccess<CpuType::Sms, MemoryType::SmsPort, MemoryOperationType::Read>(uint32_t addr, uint8_t& value);
|
||||
|
||||
template void Debugger::ProcessMemoryAccess<CpuType::Ws, MemoryType::WsPort, MemoryOperationType::Write>(uint32_t addr, uint8_t& value);
|
||||
template void Debugger::ProcessMemoryAccess<CpuType::Ws, MemoryType::WsPort, MemoryOperationType::Read>(uint32_t addr, uint8_t& value);
|
||||
template void Debugger::ProcessMemoryAccess<CpuType::Ws, MemoryType::WsPort, MemoryOperationType::Write>(uint32_t addr, uint16_t& value);
|
||||
template void Debugger::ProcessMemoryAccess<CpuType::Ws, MemoryType::WsPort, MemoryOperationType::Read>(uint32_t addr, uint16_t& value);
|
||||
template void Debugger::ProcessMemoryAccess<CpuType::Ws, MemoryType::WsInternalEeprom, MemoryOperationType::Read>(uint32_t addr, uint16_t& value);
|
||||
template void Debugger::ProcessMemoryAccess<CpuType::Ws, MemoryType::WsInternalEeprom, MemoryOperationType::Write>(uint32_t addr, uint16_t& value);
|
||||
template void Debugger::ProcessMemoryAccess<CpuType::Ws, MemoryType::WsCartEeprom, MemoryOperationType::Read>(uint32_t addr, uint16_t& value);
|
||||
template void Debugger::ProcessMemoryAccess<CpuType::Ws, MemoryType::WsCartEeprom, MemoryOperationType::Write>(uint32_t addr, uint16_t& value);
|
||||
|
||||
template void Debugger::ProcessIdleCycle<CpuType::Snes>();
|
||||
template void Debugger::ProcessIdleCycle<CpuType::Sa1>();
|
||||
|
||||
@@ -1147,6 +1199,7 @@ template void Debugger::ProcessHaltedCpu<CpuType::Spc>();
|
||||
template void Debugger::ProcessHaltedCpu<CpuType::Gameboy>();
|
||||
template void Debugger::ProcessHaltedCpu<CpuType::Sms>();
|
||||
template void Debugger::ProcessHaltedCpu<CpuType::Gba>();
|
||||
template void Debugger::ProcessHaltedCpu<CpuType::Ws>();
|
||||
|
||||
template void Debugger::ProcessInterrupt<CpuType::Snes>(uint32_t originalPc, uint32_t currentPc, bool forNmi);
|
||||
template void Debugger::ProcessInterrupt<CpuType::Sa1>(uint32_t originalPc, uint32_t currentPc, bool forNmi);
|
||||
@@ -1155,6 +1208,7 @@ template void Debugger::ProcessInterrupt<CpuType::Nes>(uint32_t originalPc, uint
|
||||
template void Debugger::ProcessInterrupt<CpuType::Pce>(uint32_t originalPc, uint32_t currentPc, bool forNmi);
|
||||
template void Debugger::ProcessInterrupt<CpuType::Sms>(uint32_t originalPc, uint32_t currentPc, bool forNmi);
|
||||
template void Debugger::ProcessInterrupt<CpuType::Gba>(uint32_t originalPc, uint32_t currentPc, bool forNmi);
|
||||
template void Debugger::ProcessInterrupt<CpuType::Ws>(uint32_t originalPc, uint32_t currentPc, bool forNmi);
|
||||
|
||||
template void Debugger::ProcessPpuRead<CpuType::Snes>(uint16_t addr, uint8_t& value, MemoryType memoryType, MemoryOperationType opType);
|
||||
template void Debugger::ProcessPpuRead<CpuType::Gameboy>(uint16_t addr, uint8_t& value, MemoryType memoryType, MemoryOperationType opType);
|
||||
@@ -1176,6 +1230,7 @@ template void Debugger::ProcessPpuCycle<CpuType::Nes>();
|
||||
template void Debugger::ProcessPpuCycle<CpuType::Pce>();
|
||||
template void Debugger::ProcessPpuCycle<CpuType::Sms>();
|
||||
template void Debugger::ProcessPpuCycle<CpuType::Gba>();
|
||||
template void Debugger::ProcessPpuCycle<CpuType::Ws>();
|
||||
|
||||
template void Debugger::ProcessBreakConditions<1>(CpuType sourceCpu, StepRequest& step, BreakpointManager* bpManager, MemoryOperationInfo& operation, AddressInfo& addressInfo);
|
||||
template void Debugger::ProcessBreakConditions<2>(CpuType sourceCpu, StepRequest& step, BreakpointManager* bpManager, MemoryOperationInfo& operation, AddressInfo& addressInfo);
|
||||
|
||||
@@ -5,6 +5,8 @@ enum class VectorType
|
||||
{
|
||||
Indirect,
|
||||
Direct,
|
||||
x86,
|
||||
x86WithOffset
|
||||
};
|
||||
|
||||
struct CpuVectorDefinition
|
||||
@@ -25,6 +27,7 @@ struct DebuggerFeatures
|
||||
bool CallStack;
|
||||
bool CpuCycleStep;
|
||||
|
||||
uint8_t IrqVectorOffset;
|
||||
uint8_t CpuVectorCount;
|
||||
CpuVectorDefinition CpuVectors[10];
|
||||
CpuVectorDefinition CpuVectors[16];
|
||||
};
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
#include "NES/NesTypes.h"
|
||||
#include "PCE/PceTypes.h"
|
||||
#include "SMS/SmsTypes.h"
|
||||
#include "WS/WsTypes.h"
|
||||
#include "WS/Debugger/WsDisUtils.h"
|
||||
#include "Shared/EmuSettings.h"
|
||||
#include "Utilities/FastString.h"
|
||||
#include "Utilities/HexUtilities.h"
|
||||
@@ -88,6 +90,7 @@ void Disassembler::ResetPrgCache()
|
||||
InitSource(MemoryType::PcePrgRom);
|
||||
InitSource(MemoryType::SmsPrgRom);
|
||||
InitSource(MemoryType::GbaPrgRom);
|
||||
InitSource(MemoryType::WsPrgRom);
|
||||
}
|
||||
|
||||
void Disassembler::InvalidateCache(AddressInfo addrInfo, CpuType type)
|
||||
@@ -561,6 +564,26 @@ void Disassembler::GetLineData(DisassemblyResult& row, CpuType type, MemoryType
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case CpuType::Ws:
|
||||
{
|
||||
WsCpuState state = (WsCpuState&)_debugger->GetCpuStateRef(lineCpuType);
|
||||
WsDisUtils::UpdateAddressCsIp(row.CpuAddress, state);
|
||||
|
||||
CodeDataLogger* cdl = cdlManager->GetCodeDataLogger(row.Address.Type);
|
||||
if(!disInfo.IsInitialized()) {
|
||||
disInfo = DisassemblyInfo(row.Address.Address, 0, CpuType::Ws, row.Address.Type, _memoryDumper);
|
||||
} else {
|
||||
data.Flags |= (!cdl || cdl->IsCode(data.AbsoluteAddress.Address)) ? LineFlags::VerifiedCode : LineFlags::UnexecutedCode;
|
||||
}
|
||||
|
||||
data.OpSize = disInfo.GetOpSize();
|
||||
data.EffectiveAddress = disInfo.GetEffectiveAddress(_debugger, &state, lineCpuType);
|
||||
if(showMemoryValues && data.EffectiveAddress.ValueSize >= 0) {
|
||||
data.Value = disInfo.GetMemoryValue(data.EffectiveAddress, _memoryDumper, memType);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!showMemoryValues) {
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "PCE/Debugger/PceDisUtils.h"
|
||||
#include "SMS/Debugger/SmsDisUtils.h"
|
||||
#include "GBA/Debugger/GbaDisUtils.h"
|
||||
#include "WS/Debugger/WsDisUtils.h"
|
||||
#include "Shared/EmuSettings.h"
|
||||
|
||||
DisassemblyInfo::DisassemblyInfo()
|
||||
@@ -38,7 +39,7 @@ void DisassemblyInfo::Initialize(uint32_t cpuAddress, uint8_t cpuFlags, CpuType
|
||||
|
||||
_opSize = GetOpSize(_byteCode[0], _flags, _cpuType, cpuAddress, memType, memoryDumper);
|
||||
|
||||
for(int i = 1; i < _opSize; i++) {
|
||||
for(int i = 1; i < _opSize && i < 8; i++) {
|
||||
_byteCode[i] = memoryDumper->GetMemoryValue(memType, cpuAddress+i);
|
||||
}
|
||||
|
||||
@@ -77,6 +78,7 @@ void DisassemblyInfo::GetDisassembly(string &out, uint32_t memoryAddr, LabelMana
|
||||
case CpuType::Pce: PceDisUtils::GetDisassembly(*this, out, memoryAddr, labelManager, settings); break;
|
||||
case CpuType::Sms: SmsDisUtils::GetDisassembly(*this, out, memoryAddr, labelManager, settings); break;
|
||||
case CpuType::Gba: GbaDisUtils::GetDisassembly(*this, out, memoryAddr, labelManager, settings); break;
|
||||
case CpuType::Ws: WsDisUtils::GetDisassembly(*this, out, memoryAddr, labelManager, settings); break;
|
||||
|
||||
default:
|
||||
throw std::runtime_error("GetDisassembly - Unsupported CPU type");
|
||||
@@ -111,6 +113,7 @@ EffectiveAddressInfo DisassemblyInfo::GetEffectiveAddress(Debugger *debugger, vo
|
||||
case CpuType::Pce: return PceDisUtils::GetEffectiveAddress(*this, (PceConsole*)debugger->GetConsole(), *(PceCpuState*)cpuState);
|
||||
case CpuType::Sms: return SmsDisUtils::GetEffectiveAddress(*this, (SmsConsole*)debugger->GetConsole(), *(SmsCpuState*)cpuState);
|
||||
case CpuType::Gba: return GbaDisUtils::GetEffectiveAddress(*this, (GbaConsole*)debugger->GetConsole(), *(GbaCpuState*)cpuState);
|
||||
case CpuType::Ws: return WsDisUtils::GetEffectiveAddress(*this, (WsConsole*)debugger->GetConsole(), *(WsCpuState*)cpuState);
|
||||
}
|
||||
|
||||
throw std::runtime_error("GetEffectiveAddress - Unsupported CPU type");
|
||||
@@ -134,6 +137,7 @@ uint32_t DisassemblyInfo::GetFullOpCode()
|
||||
case CpuType::NecDsp: return _byteCode[0] | (_byteCode[1] << 8) | (_byteCode[2] << 16);
|
||||
case CpuType::Cx4: return _byteCode[1];
|
||||
case CpuType::Gba: return _byteCode[0] | (_byteCode[1] << 8) | (_opSize == 4 ? ((_byteCode[2] << 16) | (_byteCode[3] << 24)) : 0);
|
||||
case CpuType::Ws: return WsDisUtils::GetFullOpCode(*this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,6 +187,7 @@ uint8_t DisassemblyInfo::GetOpSize(uint32_t opCode, uint8_t flags, CpuType type,
|
||||
case CpuType::Pce: return PceDisUtils::GetOpSize(opCode);
|
||||
case CpuType::Sms: return SmsDisUtils::GetOpSize(opCode, cpuAddress, memType, memoryDumper);
|
||||
case CpuType::Gba: return GbaDisUtils::GetOpSize(opCode, flags);
|
||||
case CpuType::Ws: return WsDisUtils::GetOpSize(cpuAddress, memType, memoryDumper);
|
||||
}
|
||||
|
||||
throw std::runtime_error("GetOpSize - Unsupported CPU type");
|
||||
@@ -202,6 +207,7 @@ bool DisassemblyInfo::IsJumpToSub()
|
||||
case CpuType::Pce: return PceDisUtils::IsJumpToSub(GetOpCode());
|
||||
case CpuType::Sms: return SmsDisUtils::IsJumpToSub(GetOpCode());
|
||||
case CpuType::Gba: return GbaDisUtils::IsJumpToSub(GetFullOpCode<CpuType::Gba>(), _flags);
|
||||
case CpuType::Ws: return WsDisUtils::IsJumpToSub(GetFullOpCode<CpuType::Ws>());
|
||||
}
|
||||
|
||||
throw std::runtime_error("IsJumpToSub - Unsupported CPU type");
|
||||
@@ -221,6 +227,7 @@ bool DisassemblyInfo::IsReturnInstruction()
|
||||
case CpuType::Pce: return PceDisUtils::IsReturnInstruction(GetOpCode());
|
||||
case CpuType::Sms: return SmsDisUtils::IsReturnInstruction(_byteCode[0] | (_byteCode[1] << 8));
|
||||
case CpuType::Gba: return GbaDisUtils::IsReturnInstruction(GetFullOpCode<CpuType::Gba>(), _flags);
|
||||
case CpuType::Ws: return WsDisUtils::IsReturnInstruction(GetFullOpCode<CpuType::Ws>());
|
||||
}
|
||||
|
||||
throw std::runtime_error("IsReturnInstruction - Unsupported CPU type");
|
||||
@@ -255,6 +262,7 @@ bool DisassemblyInfo::IsUnconditionalJump()
|
||||
case CpuType::Pce: return PceDisUtils::IsUnconditionalJump(GetOpCode());
|
||||
case CpuType::Sms: return SmsDisUtils::IsUnconditionalJump(GetOpCode());
|
||||
case CpuType::Gba: return GbaDisUtils::IsUnconditionalJump(GetFullOpCode<CpuType::Gba>(), _flags);
|
||||
case CpuType::Ws: return WsDisUtils::IsUnconditionalJump(GetFullOpCode<CpuType::Ws>());
|
||||
}
|
||||
|
||||
throw std::runtime_error("IsUnconditionalJump - Unsupported CPU type");
|
||||
@@ -279,6 +287,7 @@ bool DisassemblyInfo::IsJump()
|
||||
case CpuType::Pce: return PceDisUtils::IsConditionalJump(GetOpCode());
|
||||
case CpuType::Sms: return SmsDisUtils::IsConditionalJump(GetOpCode());
|
||||
case CpuType::Gba: return GbaDisUtils::IsConditionalJump(GetFullOpCode<CpuType::Gba>(), _flags);
|
||||
case CpuType::Ws: return WsDisUtils::IsConditionalJump(GetFullOpCode<CpuType::Ws>());
|
||||
}
|
||||
|
||||
throw std::runtime_error("IsJump - Unsupported CPU type");
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
#include "pch.h"
|
||||
#include "Debugger/ExpressionEvaluator.h"
|
||||
#include "WS/Debugger/WsDebugger.h"
|
||||
#include "WS/WsTypes.h"
|
||||
|
||||
unordered_map<string, int64_t>& ExpressionEvaluator::GetWsTokens()
|
||||
{
|
||||
static unordered_map<string, int64_t> supportedTokens = {
|
||||
{ "ax", EvalValues::RegAX },
|
||||
{ "bx", EvalValues::RegBX },
|
||||
{ "cx", EvalValues::RegCX },
|
||||
{ "dx", EvalValues::RegDX },
|
||||
|
||||
{ "al", EvalValues::RegAL },
|
||||
{ "bl", EvalValues::RegBL },
|
||||
{ "cl", EvalValues::RegCL },
|
||||
{ "dl", EvalValues::RegDL },
|
||||
|
||||
{ "ah", EvalValues::RegAH },
|
||||
{ "bh", EvalValues::RegBH },
|
||||
{ "ch", EvalValues::RegCH },
|
||||
{ "dh", EvalValues::RegDH },
|
||||
|
||||
{ "cs", EvalValues::RegCS },
|
||||
{ "ds", EvalValues::RegDS },
|
||||
{ "es", EvalValues::RegES },
|
||||
{ "ss", EvalValues::RegSS },
|
||||
|
||||
{ "si", EvalValues::RegSI },
|
||||
{ "di", EvalValues::RegDI },
|
||||
{ "bp", EvalValues::RegBP },
|
||||
{ "ip", EvalValues::RegIP },
|
||||
|
||||
{ "f", EvalValues::RegF },
|
||||
|
||||
{ "sp", EvalValues::RegSP },
|
||||
{ "pc", EvalValues::RegPC },
|
||||
|
||||
{ "frame", EvalValues::PpuFrameCount },
|
||||
{ "cycle", EvalValues::PpuCycle },
|
||||
{ "scanline", EvalValues::PpuScanline },
|
||||
};
|
||||
|
||||
return supportedTokens;
|
||||
}
|
||||
|
||||
int64_t ExpressionEvaluator::GetWsTokenValue(int64_t token, EvalResultType& resultType)
|
||||
{
|
||||
auto ppu = [this]() -> WsPpuState {
|
||||
WsPpuState ppu;
|
||||
((WsDebugger*)_cpuDebugger)->GetPpuState(ppu);
|
||||
return ppu;
|
||||
};
|
||||
|
||||
WsCpuState& s = (WsCpuState&)((WsDebugger*)_cpuDebugger)->GetState();
|
||||
switch(token) {
|
||||
case EvalValues::RegAX: return s.AX;
|
||||
case EvalValues::RegBX: return s.BX;
|
||||
case EvalValues::RegCX: return s.CX;
|
||||
case EvalValues::RegDX: return s.DX;
|
||||
case EvalValues::RegAL: return s.AX & 0xFF;
|
||||
case EvalValues::RegBL: return s.BX & 0xFF;
|
||||
case EvalValues::RegCL: return s.CX & 0xFF;
|
||||
case EvalValues::RegDL: return s.DX & 0xFF;
|
||||
case EvalValues::RegAH: return s.AX >> 8;
|
||||
case EvalValues::RegBH: return s.BX >> 8;
|
||||
case EvalValues::RegCH: return s.CX >> 8;
|
||||
case EvalValues::RegDH: return s.DX >> 8;
|
||||
|
||||
case EvalValues::RegCS: return s.CS;
|
||||
case EvalValues::RegDS: return s.DS;
|
||||
case EvalValues::RegES: return s.ES;
|
||||
case EvalValues::RegSS: return s.SS;
|
||||
case EvalValues::RegSI: return s.SI;
|
||||
case EvalValues::RegDI: return s.DI;
|
||||
case EvalValues::RegBP: return s.BP;
|
||||
case EvalValues::RegIP: return s.IP;
|
||||
|
||||
case EvalValues::RegF: return s.Flags.Get();
|
||||
|
||||
case EvalValues::RegSP: return s.SP;
|
||||
case EvalValues::RegPC: return (s.CS << 4) + s.IP;
|
||||
|
||||
case EvalValues::PpuFrameCount: return ppu().FrameCount;
|
||||
case EvalValues::PpuCycle: return ppu().Cycle;
|
||||
case EvalValues::PpuScanline: return ppu().Scanline;
|
||||
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
@@ -70,6 +70,7 @@ unordered_map<string, int64_t>* ExpressionEvaluator::GetAvailableTokens()
|
||||
case CpuType::Pce: return &GetPceTokens();
|
||||
case CpuType::Sms: return &GetSmsTokens();
|
||||
case CpuType::Gba: return &GetGbaTokens();
|
||||
case CpuType::Ws: return &GetWsTokens();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
@@ -156,10 +157,21 @@ string ExpressionEvaluator::GetNextToken(string expression, size_t &pos, Express
|
||||
string output;
|
||||
success = true;
|
||||
|
||||
if(expression.empty()) {
|
||||
success = false;
|
||||
return "";
|
||||
}
|
||||
|
||||
char c = std::tolower(expression[pos]);
|
||||
if(c == '$') {
|
||||
char nextChar = (pos + 1 < expression.size()) ? std::tolower(expression[pos + 1]) : '\0';
|
||||
if(c == '$' || (c == '0' && nextChar == 'x')) {
|
||||
//Hex numbers
|
||||
pos++;
|
||||
if(c == '0') {
|
||||
//Skip over 'x' in '0x' prefix
|
||||
pos++;
|
||||
}
|
||||
|
||||
for(size_t len = expression.size(); pos < len; pos++) {
|
||||
c = std::tolower(expression[pos]);
|
||||
if((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')) {
|
||||
@@ -413,6 +425,7 @@ int64_t ExpressionEvaluator::Evaluate(ExpressionData &data, EvalResultType &resu
|
||||
case CpuType::Pce: token = GetPceTokenValue(token, resultType); break;
|
||||
case CpuType::Sms: token = GetSmsTokenValue(token, resultType); break;
|
||||
case CpuType::Gba: token = GetGbaTokenValue(token, resultType); break;
|
||||
case CpuType::Ws: token = GetWsTokenValue(token, resultType); break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -177,6 +177,31 @@ enum EvalValues : int64_t
|
||||
|
||||
CPSR,
|
||||
|
||||
RegAX,
|
||||
RegBX,
|
||||
RegCX,
|
||||
RegDX,
|
||||
|
||||
RegAL,
|
||||
RegBL,
|
||||
RegCL,
|
||||
RegDL,
|
||||
|
||||
RegAH,
|
||||
RegBH,
|
||||
RegCH,
|
||||
RegDH,
|
||||
|
||||
RegCS,
|
||||
RegDS,
|
||||
RegES,
|
||||
RegSS,
|
||||
|
||||
RegSI,
|
||||
RegDI,
|
||||
RegBP,
|
||||
RegIP,
|
||||
|
||||
FirstLabelIndex,
|
||||
};
|
||||
|
||||
@@ -258,6 +283,9 @@ private:
|
||||
unordered_map<string, int64_t>& GetGbaTokens();
|
||||
int64_t GetGbaTokenValue(int64_t token, EvalResultType& resultType);
|
||||
|
||||
unordered_map<string, int64_t>& GetWsTokens();
|
||||
int64_t GetWsTokenValue(int64_t token, EvalResultType& resultType);
|
||||
|
||||
bool ReturnBool(int64_t value, EvalResultType& resultType);
|
||||
|
||||
int64_t ProcessSharedTokens(string token);
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
#include "SMS/SmsMemoryManager.h"
|
||||
#include "GBA/GbaConsole.h"
|
||||
#include "GBA/GbaMemoryManager.h"
|
||||
#include "WS/WsConsole.h"
|
||||
#include "WS/WsMemoryManager.h"
|
||||
#include "Shared/Video/VideoDecoder.h"
|
||||
#include "Debugger/DebugTypes.h"
|
||||
#include "Debugger/DebugBreakHelper.h"
|
||||
@@ -51,6 +53,8 @@ MemoryDumper::MemoryDumper(Debugger* debugger)
|
||||
_smsConsole = sms;
|
||||
} else if(GbaConsole* gba = dynamic_cast<GbaConsole*>(console)) {
|
||||
_gbaConsole = gba;
|
||||
} else if(WsConsole* ws = dynamic_cast<WsConsole*>(console)) {
|
||||
_wsConsole = ws;
|
||||
}
|
||||
|
||||
for(int i = 0; i < DebugUtilities::GetMemoryTypeCount(); i++) {
|
||||
@@ -97,8 +101,10 @@ uint32_t MemoryDumper::GetMemorySize(MemoryType type)
|
||||
case MemoryType::PceMemory: return 0x10000;
|
||||
case MemoryType::SmsMemory: return 0x10000;
|
||||
case MemoryType::GbaMemory: return 0x10000000;
|
||||
case MemoryType::WsMemory: return 0x100000;
|
||||
case MemoryType::SnesRegister: return 0x10000;
|
||||
case MemoryType::SmsPort: return 0x100;
|
||||
case MemoryType::WsPort: return 0x10000;
|
||||
default: return _emu->GetMemory(type).Size;
|
||||
}
|
||||
}
|
||||
@@ -207,6 +213,17 @@ void MemoryDumper::GetMemoryState(MemoryType type, uint8_t *buffer)
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MemoryType::WsMemory: {
|
||||
if(_wsConsole) {
|
||||
WsMemoryManager* memManager = _wsConsole->GetMemoryManager();
|
||||
for(int i = 0; i <= 0xFFFFF; i++) {
|
||||
buffer[i] = memManager->DebugRead(i);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
uint8_t* src = GetMemoryBuffer(type);
|
||||
if(src) {
|
||||
@@ -257,6 +274,7 @@ void MemoryDumper::InternalSetMemoryValues(MemoryType originalMemoryType, uint32
|
||||
case MemoryType::PceMemory: _pceConsole->GetMemoryManager()->DebugWrite(address, value); break;
|
||||
case MemoryType::SmsMemory: _smsConsole->GetMemoryManager()->DebugWrite(address, value); break;
|
||||
case MemoryType::GbaMemory: _gbaConsole->GetMemoryManager()->DebugWrite(address, value); break;
|
||||
case MemoryType::WsMemory: _wsConsole->GetMemoryManager()->DebugWrite(address, value); break;
|
||||
case MemoryType::SpcDspRegisters: _spc->DebugWriteDspReg(address, value); break;
|
||||
|
||||
default:
|
||||
@@ -349,7 +367,10 @@ uint8_t MemoryDumper::InternalGetMemoryValue(MemoryType memoryType, uint32_t add
|
||||
case MemoryType::NesPpuMemory: return _nesConsole->DebugReadVram(address);
|
||||
case MemoryType::PceMemory: return _pceConsole->GetMemoryManager()->DebugRead(address);
|
||||
case MemoryType::SmsMemory: return _smsConsole->GetMemoryManager()->DebugRead(address);
|
||||
case MemoryType::SmsPort: return _smsConsole->GetMemoryManager()->DebugReadPort(address);
|
||||
case MemoryType::GbaMemory: return _gbaConsole->GetMemoryManager()->DebugRead(address);
|
||||
case MemoryType::WsMemory: return _wsConsole->GetMemoryManager()->DebugRead(address);
|
||||
case MemoryType::WsPort: return _wsConsole->GetMemoryManager()->DebugReadPort<uint8_t>(address);
|
||||
|
||||
default:
|
||||
uint8_t* src = GetMemoryBuffer(memoryType);
|
||||
|
||||
@@ -14,6 +14,7 @@ class Gameboy;
|
||||
class PceConsole;
|
||||
class SmsConsole;
|
||||
class GbaConsole;
|
||||
class WsConsole;
|
||||
class Emulator;
|
||||
class Debugger;
|
||||
|
||||
@@ -40,6 +41,7 @@ private:
|
||||
PceConsole* _pceConsole = nullptr;
|
||||
SmsConsole* _smsConsole = nullptr;
|
||||
GbaConsole* _gbaConsole = nullptr;
|
||||
WsConsole* _wsConsole = nullptr;
|
||||
BaseCartridge* _cartridge = nullptr;
|
||||
Debugger* _debugger = nullptr;
|
||||
bool _isMemorySupported[DebugUtilities::GetMemoryTypeCount()] = {};
|
||||
|
||||
@@ -45,6 +45,7 @@ void PpuTools::GetTileView(GetTileViewOptions options, uint8_t* source, uint32_t
|
||||
|
||||
case TileFormat::GbaBpp4: InternalGetTileView<TileFormat::GbaBpp4>(options, source, srcSize, colors, outBuffer); break;
|
||||
case TileFormat::GbaBpp8: InternalGetTileView<TileFormat::GbaBpp8>(options, source, srcSize, colors, outBuffer); break;
|
||||
case TileFormat::WsBpp4Packed: InternalGetTileView<TileFormat::WsBpp4Packed>(options, source, srcSize, colors, outBuffer); break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,6 +113,8 @@ void PpuTools::InternalGetTileView(GetTileViewOptions options, uint8_t *source,
|
||||
|
||||
case TileFormat::GbaBpp4: bpp = 4; rowOffset = 4; break;
|
||||
case TileFormat::GbaBpp8: bpp = 8; rowOffset = 8; break;
|
||||
|
||||
case TileFormat::WsBpp4Packed: bpp = 4; rowOffset = 4; break;
|
||||
|
||||
default: bpp = 8; break;
|
||||
}
|
||||
@@ -243,6 +246,7 @@ void PpuTools::GetSetTilePixel(AddressInfo tileAddress, TileFormat format, int32
|
||||
case TileFormat::SmsSgBpp1: rowOffset = 1; break;
|
||||
case TileFormat::GbaBpp4: rowOffset = 4; break;
|
||||
case TileFormat::GbaBpp8: rowOffset = 8; break;
|
||||
case TileFormat::WsBpp4Packed: rowOffset = 4; break;
|
||||
}
|
||||
|
||||
uint8_t* ram = (uint8_t*)memInfo.Memory;
|
||||
@@ -377,6 +381,18 @@ void PpuTools::GetSetTilePixel(AddressInfo tileAddress, TileFormat format, int32
|
||||
break;
|
||||
}
|
||||
|
||||
case TileFormat::WsBpp4Packed: {
|
||||
uint8_t pixelOffset = (7 - shift);
|
||||
int32_t addr = (rowStart + (pixelOffset >> 1));
|
||||
if(addr <= ramMask) {
|
||||
int offset = pixelOffset & 0x01 ? 0 : 4;
|
||||
for(int i = 0; i < 4; i++) {
|
||||
setBit(addr, i+offset, i);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw std::runtime_error("unsupported format");
|
||||
}
|
||||
|
||||
@@ -184,12 +184,14 @@ enum class RawPaletteFormat
|
||||
Rgb555,
|
||||
Rgb333,
|
||||
Rgb222,
|
||||
Rgb444
|
||||
Rgb444,
|
||||
Bgr444
|
||||
};
|
||||
|
||||
struct DebugPaletteInfo
|
||||
{
|
||||
MemoryType PaletteMemType;
|
||||
uint32_t PaletteMemOffset;
|
||||
bool HasMemType;
|
||||
|
||||
uint32_t ColorCount;
|
||||
@@ -281,6 +283,7 @@ template<TileFormat format> uint32_t PpuTools::GetRgbPixelColor(const uint32_t*
|
||||
case TileFormat::Bpp4:
|
||||
case TileFormat::SmsBpp4:
|
||||
case TileFormat::GbaBpp4:
|
||||
case TileFormat::WsBpp4Packed:
|
||||
case TileFormat::PceSpriteBpp4:
|
||||
case TileFormat::PceSpriteBpp2Sp01:
|
||||
case TileFormat::PceSpriteBpp2Sp23:
|
||||
@@ -423,6 +426,20 @@ template<TileFormat format> uint8_t PpuTools::GetTilePixelColor(const uint8_t* r
|
||||
}
|
||||
}
|
||||
|
||||
case TileFormat::WsBpp4Packed: {
|
||||
uint8_t pixelOffset = (7 - shift);
|
||||
uint32_t addr = (rowStart + (pixelOffset >> 1));
|
||||
if(addr <= ramMask) {
|
||||
if(pixelOffset & 0x01) {
|
||||
return ram[addr] & 0x0F;
|
||||
} else {
|
||||
return ram[addr] >> 4;
|
||||
}
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
throw std::runtime_error("unsupported format");
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ struct GbaGpioState
|
||||
bool ReadWrite;
|
||||
};
|
||||
|
||||
class GbaGpio : public ISerializable
|
||||
class GbaGpio final : public ISerializable
|
||||
{
|
||||
private:
|
||||
GbaGpioState _state = {};
|
||||
|
||||
@@ -21,7 +21,7 @@ struct GbaRtcState
|
||||
bool TestMode; //unimplemented
|
||||
};
|
||||
|
||||
class GbaRtc : public ISerializable
|
||||
class GbaRtc final : public ISerializable
|
||||
{
|
||||
private:
|
||||
enum class Command : uint8_t
|
||||
|
||||
@@ -21,8 +21,8 @@ private:
|
||||
string mapperId = footer.GetMapperId();
|
||||
|
||||
MessageManager::Log("[GBX] Mapper: " + mapperId);
|
||||
MessageManager::Log(string("[GBX] ROM Size: ") + std::to_string(footer.GetRomSize() / 1024) + " kB");
|
||||
MessageManager::Log(string("[GBX] RAM Size: ") + std::to_string(footer.GetRamSize() / 1024) + " kB");
|
||||
MessageManager::Log(string("[GBX] ROM Size: ") + std::to_string(footer.GetRomSize() / 1024) + " KB");
|
||||
MessageManager::Log(string("[GBX] RAM Size: ") + std::to_string(footer.GetRamSize() / 1024) + " KB");
|
||||
MessageManager::Log("[GBX] Battery: " + string(footer.HasBattery() ? "Yes" : "No"));
|
||||
MessageManager::Log("[GBX] RTC: " + string(footer.HasRtc() ? "Yes" : "No"));
|
||||
MessageManager::Log("[GBX] Rumble: " + string(footer.HasRumble() ? "Yes" : "No"));
|
||||
|
||||
@@ -13,8 +13,8 @@ GbDefaultVideoFilter::GbDefaultVideoFilter(Emulator* emu, bool applyNtscFilter)
|
||||
{
|
||||
InitLookupTable();
|
||||
_applyNtscFilter = applyNtscFilter;
|
||||
_prevFrame = new uint16_t[160 * 144];
|
||||
memset(_prevFrame, 0, 160 * 144 * sizeof(uint16_t));
|
||||
_prevFrame = new uint16_t[GbConstants::PixelCount];
|
||||
memset(_prevFrame, 0, GbConstants::PixelCount * sizeof(uint16_t));
|
||||
}
|
||||
|
||||
GbDefaultVideoFilter::~GbDefaultVideoFilter()
|
||||
@@ -88,7 +88,7 @@ void GbDefaultVideoFilter::OnBeforeApplyFilter()
|
||||
bool blendFrames = gbConfig.BlendFrames && !_emu->GetRewindManager()->IsRewinding() && !_emu->IsPaused();
|
||||
if(_blendFrames != blendFrames) {
|
||||
_blendFrames = blendFrames;
|
||||
memset(_prevFrame, 0, 160 * 144 * sizeof(uint16_t));
|
||||
memset(_prevFrame, 0, GbConstants::PixelCount * sizeof(uint16_t));
|
||||
}
|
||||
_videoConfig = config;
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ protected:
|
||||
}
|
||||
|
||||
if(_microphoneEnabled && (_emu->GetFrameCount() % 3) == 0) {
|
||||
SetPressedState(Buttons::Microphone, keyMapping.Microphone);
|
||||
SetPressedState(Buttons::Microphone, keyMapping.GenericKey1);
|
||||
}
|
||||
|
||||
if(!_emu->GetSettings()->GetNesConfig().AllowInvalidInput) {
|
||||
|
||||
@@ -9,6 +9,11 @@ public:
|
||||
return (color << 3) + (color >> 2);
|
||||
}
|
||||
|
||||
static uint8_t Convert4BitTo8Bit(uint8_t color)
|
||||
{
|
||||
return (color << 4) | color;
|
||||
}
|
||||
|
||||
static uint32_t Rgb555ToArgb(uint16_t rgb555)
|
||||
{
|
||||
uint8_t b = Convert5BitTo8Bit((rgb555 >> 10) & 0x1F);
|
||||
@@ -46,6 +51,14 @@ public:
|
||||
return Rgb555ToArgb(Rgb444To555(rgb444));
|
||||
}
|
||||
|
||||
static uint32_t Bgr444ToArgb(uint16_t bgr444)
|
||||
{
|
||||
uint8_t b = (bgr444 & 0x00F);
|
||||
uint8_t g = (bgr444 & 0x0F0) >> 4;
|
||||
uint8_t r = (bgr444 & 0xF00) >> 8;
|
||||
return 0xFF000000 | (r << 20) | (r << 16) | (g << 12) | (g << 8) | (b << 4) | (b << 0);
|
||||
}
|
||||
|
||||
static uint16_t Rgb888To555(uint32_t rgb888)
|
||||
{
|
||||
uint8_t r = (rgb888 >> 19) & 0x1F;
|
||||
|
||||
@@ -13,7 +13,8 @@ enum class CpuType : uint8_t
|
||||
Nes,
|
||||
Pce,
|
||||
Sms,
|
||||
Gba
|
||||
Gba,
|
||||
Ws
|
||||
};
|
||||
|
||||
class CpuTypeUtilities
|
||||
@@ -21,6 +22,6 @@ class CpuTypeUtilities
|
||||
public:
|
||||
static constexpr int GetCpuTypeCount()
|
||||
{
|
||||
return (int)CpuType::Sms + 1;
|
||||
return (int)CpuType::Ws + 1;
|
||||
}
|
||||
};
|
||||
@@ -119,6 +119,10 @@ void EmuSettings::Serialize(Serializer& s)
|
||||
SV(_gba.Controller.Type);
|
||||
break;
|
||||
|
||||
case ConsoleType::Ws:
|
||||
//TODOWS
|
||||
break;
|
||||
|
||||
default:
|
||||
throw std::runtime_error("unsupported console type");
|
||||
}
|
||||
@@ -261,6 +265,16 @@ CvConfig& EmuSettings::GetCvConfig()
|
||||
return _cv;
|
||||
}
|
||||
|
||||
void EmuSettings::SetWsConfig(WsConfig& config)
|
||||
{
|
||||
_ws = config;
|
||||
}
|
||||
|
||||
WsConfig& EmuSettings::GetWsConfig()
|
||||
{
|
||||
return _ws;
|
||||
}
|
||||
|
||||
void EmuSettings::SetGameConfig(GameConfig& config)
|
||||
{
|
||||
_game = config;
|
||||
@@ -422,6 +436,7 @@ OverscanDimensions EmuSettings::GetOverscan()
|
||||
|
||||
case ConsoleType::Gameboy:
|
||||
case ConsoleType::Gba:
|
||||
case ConsoleType::Ws:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -450,8 +465,8 @@ double EmuSettings::GetAspectRatio(ConsoleRegion region, FrameInfo baseFrameSize
|
||||
|
||||
//For auto, ntsc and pal, these are PAR ratios, so multiply them with the base screen's aspect ratio to get the expected screen aspect ratio
|
||||
case VideoAspectRatio::Auto:
|
||||
if(_emu->GetConsoleType() == ConsoleType::Gameboy || _emu->GetConsoleType() == ConsoleType::Gba) {
|
||||
//GB shouldn't use NTSC/PAL aspect ratio when in auto mode
|
||||
if(_emu->GetConsoleType() == ConsoleType::Gameboy || _emu->GetConsoleType() == ConsoleType::Gba || _emu->GetConsoleType() == ConsoleType::Ws) {
|
||||
//GB/GBA/WS shouldn't use NTSC/PAL aspect ratio when in auto mode
|
||||
return screenAspectRatio;
|
||||
}
|
||||
return screenAspectRatio * ((region == ConsoleRegion::Pal || region == ConsoleRegion::Dendy) ? (11.0 / 8.0) : (8.0 / 7.0));
|
||||
|
||||
@@ -30,6 +30,7 @@ private:
|
||||
SmsConfig _sms;
|
||||
CvConfig _cv;
|
||||
GbaConfig _gba;
|
||||
WsConfig _ws;
|
||||
|
||||
atomic<uint32_t> _flags;
|
||||
atomic<uint64_t> _debuggerFlags;
|
||||
@@ -92,6 +93,9 @@ public:
|
||||
void SetCvConfig(CvConfig& config);
|
||||
CvConfig& GetCvConfig();
|
||||
|
||||
void SetWsConfig(WsConfig& config);
|
||||
WsConfig& GetWsConfig();
|
||||
|
||||
void SetGameConfig(GameConfig& config);
|
||||
GameConfig& GetGameConfig();
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
#include "PCE/PceConsole.h"
|
||||
#include "SMS/SmsConsole.h"
|
||||
#include "GBA/GbaConsole.h"
|
||||
#include "WS/WsConsole.h"
|
||||
#include "Debugger/Debugger.h"
|
||||
#include "Debugger/BaseEventManager.h"
|
||||
#include "Debugger/DebugTypes.h"
|
||||
@@ -554,6 +555,7 @@ void Emulator::TryLoadRom(VirtualFile& romFile, LoadRomResult& result, unique_pt
|
||||
TryLoadRom<PceConsole>(romFile, result, console, useFileSignature);
|
||||
TryLoadRom<SmsConsole>(romFile, result, console, useFileSignature);
|
||||
TryLoadRom<GbaConsole>(romFile, result, console, useFileSignature);
|
||||
TryLoadRom<WsConsole>(romFile, result, console, useFileSignature);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
@@ -906,11 +908,11 @@ void Emulator::Serialize(ostream& out, bool includeSettings, int compressionLeve
|
||||
s.SaveTo(out, compressionLevel);
|
||||
}
|
||||
|
||||
bool Emulator::Deserialize(istream& in, uint32_t fileFormatVersion, bool includeSettings, optional<ConsoleType> srcConsoleType, bool sendNotification)
|
||||
DeserializeResult Emulator::Deserialize(istream& in, uint32_t fileFormatVersion, bool includeSettings, optional<ConsoleType> srcConsoleType, bool sendNotification)
|
||||
{
|
||||
Serializer s(fileFormatVersion, false);
|
||||
if(!s.LoadFrom(in)) {
|
||||
return false;
|
||||
return DeserializeResult::InvalidFile;
|
||||
}
|
||||
|
||||
if(includeSettings) {
|
||||
@@ -922,7 +924,7 @@ bool Emulator::Deserialize(istream& in, uint32_t fileFormatVersion, bool include
|
||||
SaveStateCompatInfo compatInfo = _console->ValidateSaveStateCompatibility(srcConsoleType.value());
|
||||
if(!compatInfo.IsCompatible) {
|
||||
MessageManager::DisplayMessage("SaveStates", "SaveStateWrongSystem");
|
||||
return false;
|
||||
return DeserializeResult::SpecificError;
|
||||
}
|
||||
|
||||
s.RemoveKeys(compatInfo.FieldsToRemove);
|
||||
@@ -935,16 +937,19 @@ bool Emulator::Deserialize(istream& in, uint32_t fileFormatVersion, bool include
|
||||
|
||||
if(!s.IsValid()) {
|
||||
MessageManager::DisplayMessage("SaveStates", "SaveStateWrongSystem");
|
||||
return false;
|
||||
return DeserializeResult::SpecificError;
|
||||
}
|
||||
}
|
||||
|
||||
s.Stream(_console, "");
|
||||
|
||||
if(s.HasError()) {
|
||||
return DeserializeResult::SpecificError;
|
||||
}
|
||||
|
||||
if(sendNotification) {
|
||||
_notificationManager->SendNotification(ConsoleNotificationType::StateLoaded);
|
||||
}
|
||||
return true;
|
||||
return DeserializeResult::Success;
|
||||
}
|
||||
|
||||
BaseVideoFilter* Emulator::GetVideoFilter(bool getDefaultFilter)
|
||||
@@ -953,6 +958,14 @@ BaseVideoFilter* Emulator::GetVideoFilter(bool getDefaultFilter)
|
||||
return console ? console->GetVideoFilter(getDefaultFilter) : new SnesDefaultVideoFilter(this);
|
||||
}
|
||||
|
||||
void Emulator::GetScreenRotationOverride(uint32_t& rotation)
|
||||
{
|
||||
shared_ptr<IConsole> console = GetConsole();
|
||||
if(console) {
|
||||
console->GetScreenRotationOverride(rotation);
|
||||
}
|
||||
}
|
||||
|
||||
void Emulator::InputBarcode(uint64_t barcode, uint32_t digitCount)
|
||||
{
|
||||
shared_ptr<IConsole> console = GetConsole();
|
||||
|
||||
@@ -183,7 +183,7 @@ public:
|
||||
void SuspendDebugger(bool release);
|
||||
|
||||
void Serialize(ostream& out, bool includeSettings, int compressionLevel = 1);
|
||||
bool Deserialize(istream& in, uint32_t fileFormatVersion, bool includeSettings, optional<ConsoleType> consoleType = std::nullopt, bool sendNotification = true);
|
||||
DeserializeResult Deserialize(istream& in, uint32_t fileFormatVersion, bool includeSettings, optional<ConsoleType> consoleType = std::nullopt, bool sendNotification = true);
|
||||
|
||||
SoundMixer* GetSoundMixer() { return _soundMixer.get(); }
|
||||
VideoRenderer* GetVideoRenderer() { return _videoRenderer.get(); }
|
||||
@@ -204,6 +204,7 @@ public:
|
||||
shared_ptr<SystemActionManager> GetSystemActionManager() { return _systemActionManager; }
|
||||
|
||||
BaseVideoFilter* GetVideoFilter(bool getDefaultFilter = false);
|
||||
void GetScreenRotationOverride(uint32_t& rotation);
|
||||
|
||||
void InputBarcode(uint64_t barcode, uint32_t digitCount);
|
||||
void ProcessTapeRecorderAction(TapeRecorderAction action, string filename);
|
||||
@@ -268,10 +269,10 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
template<CpuType cpuType, MemoryType memType, MemoryOperationType opType> __forceinline void ProcessMemoryAccess(uint32_t addr, uint8_t value)
|
||||
template<CpuType cpuType, MemoryType memType, MemoryOperationType opType, typename T> __forceinline void ProcessMemoryAccess(uint32_t addr, T value)
|
||||
{
|
||||
if(_debugger) {
|
||||
_debugger->ProcessMemoryAccess<cpuType, memType, opType>(addr, value);
|
||||
_debugger->ProcessMemoryAccess<cpuType, memType, opType, T>(addr, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,10 @@ enum class FirmwareType
|
||||
StudyBox,
|
||||
PceSuperCd,
|
||||
PceGamesExpress,
|
||||
ColecoVision
|
||||
ColecoVision,
|
||||
WonderSwan,
|
||||
WonderSwanColor,
|
||||
SwanCrystal,
|
||||
};
|
||||
|
||||
struct MissingFirmwareMessage
|
||||
@@ -114,6 +117,18 @@ struct MissingFirmwareMessage
|
||||
FileHashes[1] = "FB4A898EB93B19B36773D87A6C70EB28F981B2686BEBDD2D431B05DCDF9CFFD4";
|
||||
break;
|
||||
|
||||
case FirmwareType::WonderSwan:
|
||||
FileHashes[0] = "BF4480DBEA1C47C8B54CE7BE9382BC1006148F431FBE4277E136351FA74F635E";
|
||||
break;
|
||||
|
||||
case FirmwareType::WonderSwanColor:
|
||||
FileHashes[0] = "F5A5C044D84CE1681F94E9EF74287CB989784497BE5BD5108DF17908DFA55DB2";
|
||||
break;
|
||||
|
||||
case FirmwareType::SwanCrystal:
|
||||
FileHashes[0] = "82E96ADDF5AB1CE09A84B6EEDAA904E4CA432756851F7E0CC0649006C183834D";
|
||||
break;
|
||||
|
||||
default:
|
||||
throw std::runtime_error("Unexpected firmware type");
|
||||
}
|
||||
@@ -410,4 +425,31 @@ public:
|
||||
MessageManager::DisplayMessage("Error", "Could not find firmware file for the ColecoVision");
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool LoadWsBootRom(Emulator* emu, vector<uint8_t>& bootRom, WsModel model)
|
||||
{
|
||||
string filename;
|
||||
FirmwareType firmwareType;
|
||||
switch(model) {
|
||||
default:
|
||||
case WsModel::Monochrome: filename = "bootrom.ws"; firmwareType = FirmwareType::WonderSwan; break;
|
||||
case WsModel::Color: filename = "bootrom.wsc"; firmwareType = FirmwareType::WonderSwanColor; break;
|
||||
case WsModel::SwanCrystal: filename = "bootrom_sc.wsc"; firmwareType = FirmwareType::SwanCrystal; break;
|
||||
}
|
||||
uint32_t size = model == WsModel::Monochrome ? 0x1000 : 0x2000;
|
||||
string path = FolderUtilities::CombinePath(FolderUtilities::GetFirmwareFolder(), filename);
|
||||
if(AttemptLoadFirmware(bootRom, filename, size)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
MissingFirmwareMessage msg(filename.c_str(), firmwareType, size);
|
||||
emu->GetNotificationManager()->SendNotification(ConsoleNotificationType::MissingFirmware, &msg);
|
||||
|
||||
if(AttemptLoadFirmware(bootRom, filename, size)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
MessageManager::DisplayMessage("Error", "Could not find boot rom for the WonderSwan, skipping boot screen.");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@@ -88,6 +88,7 @@ public:
|
||||
}
|
||||
|
||||
virtual BaseVideoFilter* GetVideoFilter(bool getDefaultFilter) = 0;
|
||||
virtual void GetScreenRotationOverride(uint32_t& rotation) {}
|
||||
|
||||
virtual PpuFrameInfo GetPpuFrame() = 0;
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ enum class MemoryType
|
||||
PceMemory,
|
||||
SmsMemory,
|
||||
GbaMemory,
|
||||
WsMemory,
|
||||
|
||||
SnesPrgRom,
|
||||
SnesWorkRam,
|
||||
@@ -82,6 +83,14 @@ enum class MemoryType
|
||||
GbaVideoRam,
|
||||
GbaSpriteRam,
|
||||
GbaPaletteRam,
|
||||
|
||||
WsPrgRom,
|
||||
WsWorkRam,
|
||||
WsCartRam,
|
||||
WsCartEeprom,
|
||||
WsBootRom,
|
||||
WsInternalEeprom,
|
||||
WsPort,
|
||||
|
||||
None
|
||||
};
|
||||
@@ -30,6 +30,8 @@ enum class RomFormat
|
||||
ColecoVision,
|
||||
|
||||
Gba,
|
||||
|
||||
Ws,
|
||||
};
|
||||
|
||||
struct DipSwitchInfo
|
||||
|
||||
@@ -199,7 +199,9 @@ bool SaveStateManager::LoadState(istream &stream)
|
||||
stream.read(nameBuffer.data(), nameBuffer.size());
|
||||
string romName(nameBuffer.data(), nameLength);
|
||||
|
||||
if(_emu->Deserialize(stream, fileFormatVersion, false, stateConsoleType)) {
|
||||
DeserializeResult result = _emu->Deserialize(stream, fileFormatVersion, false, stateConsoleType);
|
||||
|
||||
if(result == DeserializeResult::Success) {
|
||||
//Stop any movie that might have been playing/recording if a state is loaded
|
||||
//(Note: Loading a state is disabled in the UI while a movie is playing/recording)
|
||||
_emu->GetMovieManager()->Stop();
|
||||
@@ -211,6 +213,8 @@ bool SaveStateManager::LoadState(istream &stream)
|
||||
_emu->GetVideoDecoder()->UpdateFrame(frame, true, false);
|
||||
}
|
||||
return true;
|
||||
} else if(result == DeserializeResult::SpecificError) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -233,6 +233,10 @@ enum class ControllerType
|
||||
|
||||
//GBA
|
||||
GbaController,
|
||||
|
||||
//WS
|
||||
WsController,
|
||||
WsControllerVertical
|
||||
};
|
||||
|
||||
struct KeyMapping
|
||||
@@ -249,6 +253,8 @@ struct KeyMapping
|
||||
uint16_t Right = 0;
|
||||
uint16_t Start = 0;
|
||||
uint16_t Select = 0;
|
||||
uint16_t U = 0;
|
||||
uint16_t D = 0;
|
||||
|
||||
uint16_t TurboA = 0;
|
||||
uint16_t TurboB = 0;
|
||||
@@ -259,13 +265,13 @@ struct KeyMapping
|
||||
uint16_t TurboSelect = 0;
|
||||
uint16_t TurboStart = 0;
|
||||
|
||||
uint16_t Microphone = 0;
|
||||
uint16_t GenericKey1 = 0;
|
||||
|
||||
uint16_t CustomKeys[100] = {};
|
||||
|
||||
bool HasKeySet()
|
||||
{
|
||||
if(A || B || X || Y || L || R || Up || Down || Left || Right || Start || Select || TurboA || TurboB || TurboX || TurboY || TurboL || TurboR || TurboStart || TurboSelect || Microphone) {
|
||||
if(A || B || X || Y || L || R || U || D || Up || Down || Left || Right || Start || Select || TurboA || TurboB || TurboX || TurboY || TurboL || TurboR || TurboStart || TurboSelect || GenericKey1) {
|
||||
return true;
|
||||
}
|
||||
for(uint32_t i = 0; i < 100; i++) {
|
||||
@@ -353,7 +359,8 @@ enum class ConsoleType
|
||||
Nes = 2,
|
||||
PcEngine = 3,
|
||||
Sms = 4,
|
||||
Gba = 5
|
||||
Gba = 5,
|
||||
Ws = 6
|
||||
};
|
||||
|
||||
enum class GameboyModel
|
||||
@@ -704,6 +711,45 @@ struct CvConfig
|
||||
uint32_t ChannelVolumes[4] = {};
|
||||
};
|
||||
|
||||
enum class WsModel : uint8_t
|
||||
{
|
||||
Auto,
|
||||
Monochrome,
|
||||
Color,
|
||||
SwanCrystal
|
||||
};
|
||||
|
||||
enum class WsAudioMode : uint8_t
|
||||
{
|
||||
Headphones,
|
||||
Speakers
|
||||
};
|
||||
|
||||
struct WsConfig
|
||||
{
|
||||
ControllerConfig ControllerHorizontal;
|
||||
ControllerConfig ControllerVertical;
|
||||
|
||||
WsModel Model = WsModel::Auto;
|
||||
bool UseBootRom = false;
|
||||
|
||||
bool AutoRotate = false;
|
||||
|
||||
bool BlendFrames = false;
|
||||
bool LcdAdjustColors = false;
|
||||
bool LcdShowIcons = false;
|
||||
|
||||
bool HideBgLayers[2] = {};
|
||||
bool DisableSprites = false;
|
||||
|
||||
WsAudioMode AudioMode = WsAudioMode::Headphones;
|
||||
uint32_t Channel1Vol = 100;
|
||||
uint32_t Channel2Vol = 100;
|
||||
uint32_t Channel3Vol = 100;
|
||||
uint32_t Channel4Vol = 100;
|
||||
uint32_t Channel5Vol = 100;
|
||||
};
|
||||
|
||||
struct AudioPlayerConfig
|
||||
{
|
||||
uint32_t Volume = 100;
|
||||
@@ -764,6 +810,8 @@ struct DebugConfig
|
||||
bool GbaBreakOnNopLoad = false;
|
||||
bool GbaBreakOnInvalidOpCode = false;
|
||||
bool GbaBreakOnUnalignedMemAccess = false;
|
||||
|
||||
bool WsBreakOnInvalidOpCode = false;
|
||||
|
||||
bool ScriptAllowIoOsAccess = false;
|
||||
bool ScriptAllowNetworkAccess = false;
|
||||
@@ -790,7 +838,7 @@ struct PreferencesConfig
|
||||
bool ShowTurboRewindIcons = false;
|
||||
bool DisableGameSelectionScreen = false;
|
||||
|
||||
HudDisplaySize HudSize;
|
||||
HudDisplaySize HudSize = HudDisplaySize::Fixed;
|
||||
|
||||
uint32_t AutoSaveStateDelay = 5;
|
||||
uint32_t RewindBufferSize = 300;
|
||||
@@ -1008,4 +1056,5 @@ enum class DebuggerFlags
|
||||
PceDebuggerEnabled = (1 << 8),
|
||||
SmsDebuggerEnabled = (1 << 9),
|
||||
GbaDebuggerEnabled = (1 << 10),
|
||||
WsDebuggerEnabled = (1 << 11),
|
||||
};
|
||||
|
||||
@@ -184,6 +184,8 @@ void BaseVideoFilter::TakeScreenshot(VideoFilterType filterType, string filename
|
||||
uint8_t scale = 1;
|
||||
|
||||
uint32_t screenRotation = _emu->GetSettings()->GetVideoConfig().ScreenRotation;
|
||||
_emu->GetScreenRotationOverride(screenRotation);
|
||||
|
||||
unique_ptr<RotateFilter> rotateFilter(new RotateFilter(screenRotation));
|
||||
if(screenRotation != 0) {
|
||||
pngBuffer = rotateFilter->ApplyFilter(pngBuffer, frameInfo.Width, frameInfo.Height);
|
||||
|
||||
@@ -48,6 +48,19 @@ void ScaleFilter::ApplyLcdGridFilter(uint32_t* inputArgbBuffer)
|
||||
uint8_t bottomLeft = (uint8_t)(cfg.LcdGridBottomLeftBrightness * 255);
|
||||
uint8_t bottomRight = (uint8_t)(cfg.LcdGridBottomRightBrightness * 255);
|
||||
|
||||
//Rotate lcd effect as needed
|
||||
uint32_t screenRotation = _emu->GetSettings()->GetVideoConfig().ScreenRotation;
|
||||
_emu->GetScreenRotationOverride(screenRotation);
|
||||
|
||||
uint8_t offset = screenRotation / 90;
|
||||
for(int i = 0; i < offset; i++) {
|
||||
uint8_t orgTopLeft = topLeft;
|
||||
topLeft = topRight;
|
||||
topRight = bottomRight;
|
||||
bottomRight = bottomLeft;
|
||||
bottomLeft = orgTopLeft;
|
||||
}
|
||||
|
||||
for(uint32_t y = 0; y < _height; y++) {
|
||||
for(uint32_t x = 0; x < _width; x++) {
|
||||
uint32_t srcColor = inputArgbBuffer[y * _width + x];
|
||||
|
||||
@@ -81,6 +81,8 @@ void VideoDecoder::UpdateVideoFilter()
|
||||
}
|
||||
|
||||
uint32_t screenRotation = _emu->GetSettings()->GetVideoConfig().ScreenRotation;
|
||||
_emu->GetScreenRotationOverride(screenRotation);
|
||||
|
||||
if(screenRotation != 0) {
|
||||
if(!_rotateFilter || _rotateFilter->GetAngle() != screenRotation) {
|
||||
_rotateFilter.reset(new RotateFilter(screenRotation));
|
||||
|
||||
@@ -0,0 +1,468 @@
|
||||
#include "pch.h"
|
||||
#include "WS/APU/WsApu.h"
|
||||
#include "WS/APU/WsApuCh1.h"
|
||||
#include "WS/APU/WsApuCh2.h"
|
||||
#include "WS/APU/WsApuCh3.h"
|
||||
#include "WS/APU/WsApuCh4.h"
|
||||
#include "WS/APU/WsHyperVoice.h"
|
||||
#include "WS/WsConsole.h"
|
||||
#include "WS/WsPpu.h"
|
||||
#include "WS/WsDmaController.h"
|
||||
#include "WS/WsMemoryManager.h"
|
||||
#include "Shared/Emulator.h"
|
||||
#include "Shared/EmuSettings.h"
|
||||
#include "Shared/Audio/SoundMixer.h"
|
||||
#include "Utilities/BitUtilities.h"
|
||||
#include "Utilities/Serializer.h"
|
||||
|
||||
WsApu::WsApu(Emulator* emu, WsConsole* console, WsMemoryManager* memoryManager, WsDmaController* dmaController)
|
||||
{
|
||||
_emu = emu;
|
||||
_console = console;
|
||||
_memoryManager = memoryManager;
|
||||
_dmaController = dmaController;
|
||||
_soundMixer = emu->GetSoundMixer();
|
||||
|
||||
_state.MasterVolume = _console->GetModel() == WsModel::Monochrome ? 2 : 3;
|
||||
_state.InternalMasterVolume = _state.MasterVolume;
|
||||
|
||||
_ch1.reset(new WsApuCh1(this, _state.Ch1));
|
||||
_ch2.reset(new WsApuCh2(this, _state.Ch2));
|
||||
_ch3.reset(new WsApuCh3(this, _state.Ch3));
|
||||
_ch4.reset(new WsApuCh4(this, _state.Ch4));
|
||||
_hyperVoice.reset(new WsHyperVoice(_state.Voice));
|
||||
|
||||
_soundBuffer = new int16_t[WsApu::MaxSamples * 2];
|
||||
memset(_soundBuffer, 0, WsApu::MaxSamples * 2 * sizeof(int16_t));
|
||||
|
||||
_filterL.SetCutoffFrequency(16, WsApu::ApuFrequency);
|
||||
_filterR.SetCutoffFrequency(16, WsApu::ApuFrequency);
|
||||
}
|
||||
|
||||
WsApu::~WsApu()
|
||||
{
|
||||
delete[] _soundBuffer;
|
||||
}
|
||||
|
||||
void WsApu::ChangeMasterVolume()
|
||||
{
|
||||
if(_emu->GetSettings()->GetWsConfig().AudioMode == WsAudioMode::Speakers) {
|
||||
if(_state.InternalMasterVolume == 0) {
|
||||
_state.InternalMasterVolume = _console->GetModel() == WsModel::Monochrome ? 2 : 3;
|
||||
} else {
|
||||
_state.InternalMasterVolume--;
|
||||
}
|
||||
}
|
||||
_console->GetPpu()->ShowVolumeIcon();
|
||||
}
|
||||
|
||||
void WsApu::PlayQueuedAudio()
|
||||
{
|
||||
_soundMixer->PlayAudioBuffer(_soundBuffer, _sampleCount, WsApu::ApuFrequency);
|
||||
_sampleCount = 0;
|
||||
_clockCounter = 0;
|
||||
}
|
||||
|
||||
void WsApu::WriteDma(bool forHyperVoice, uint8_t sampleValue)
|
||||
{
|
||||
if(forHyperVoice) {
|
||||
_hyperVoice->WriteDma(sampleValue);
|
||||
} else {
|
||||
_state.Ch2.RightVolume = sampleValue & 0x0F;
|
||||
_state.Ch2.LeftVolume = sampleValue >> 4;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t WsApu::ReadSample(uint8_t ch, uint8_t pos)
|
||||
{
|
||||
//TODOWS review once the exact behavior for these 2 flags is understood
|
||||
if(_state.ForceOutput4) {
|
||||
return 4;
|
||||
} else if(_state.ForceOutput2) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
uint32_t addr = _state.WaveTableAddress + (ch * 16) + (pos >> 1);
|
||||
uint8_t value = _memoryManager->InternalRead(addr);
|
||||
_emu->ProcessMemoryRead<CpuType::Ws>(addr, value, MemoryOperationType::Read);
|
||||
if(pos & 0x01) {
|
||||
return value & 0x0F;
|
||||
} else {
|
||||
return value >> 4;
|
||||
}
|
||||
}
|
||||
|
||||
void WsApu::UpdateOutput()
|
||||
{
|
||||
WsConfig& cfg = _emu->GetSettings()->GetWsConfig();
|
||||
|
||||
int32_t leftOutput = (
|
||||
_state.Ch1.LeftOutput * cfg.Channel1Vol / 100 +
|
||||
_state.Ch2.LeftOutput * cfg.Channel2Vol / 100 +
|
||||
_state.Ch3.LeftOutput * cfg.Channel3Vol / 100 +
|
||||
_state.Ch4.LeftOutput * cfg.Channel4Vol / 100
|
||||
);
|
||||
|
||||
int32_t rightOutput = (
|
||||
_state.Ch1.RightOutput * cfg.Channel1Vol / 100 +
|
||||
_state.Ch2.RightOutput * cfg.Channel2Vol / 100 +
|
||||
_state.Ch3.RightOutput * cfg.Channel3Vol / 100 +
|
||||
_state.Ch4.RightOutput * cfg.Channel4Vol / 100
|
||||
);
|
||||
|
||||
if(_state.ForceOutputCh2Voice) {
|
||||
leftOutput = (_state.Ch2.GetVolume() * 5) & 0x3FF;
|
||||
rightOutput = leftOutput;
|
||||
}
|
||||
|
||||
if(cfg.AudioMode == WsAudioMode::Headphones) {
|
||||
if(_state.HeadphoneEnabled) {
|
||||
leftOutput <<= 5;
|
||||
rightOutput <<= 5;
|
||||
|
||||
if(_state.Voice.Enabled) {
|
||||
leftOutput += _state.Voice.LeftOutput * cfg.Channel5Vol / 100;
|
||||
rightOutput += _state.Voice.RightOutput * cfg.Channel5Vol / 100;
|
||||
}
|
||||
} else {
|
||||
leftOutput = 0;
|
||||
rightOutput = 0;
|
||||
}
|
||||
} else {
|
||||
if(_state.SpeakerEnabled) {
|
||||
int32_t out = (((leftOutput + rightOutput) >> _state.SpeakerVolume) & 0xFF) << 7;
|
||||
leftOutput = out;
|
||||
rightOutput = out;
|
||||
|
||||
if(_console->GetModel() == WsModel::Monochrome) {
|
||||
switch(_state.InternalMasterVolume) {
|
||||
case 0: leftOutput = 0; rightOutput = 0; break;
|
||||
case 1: leftOutput >>= 1; rightOutput >>= 1; break;
|
||||
case 2: break;
|
||||
}
|
||||
} else {
|
||||
switch(_state.InternalMasterVolume) {
|
||||
case 0: leftOutput = 0; rightOutput = 0; break;
|
||||
case 1: leftOutput /= 3; rightOutput /= 3; break;
|
||||
case 2: leftOutput = leftOutput * 2 / 3; rightOutput = rightOutput * 2 / 3; break;
|
||||
case 3: break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
leftOutput = 0;
|
||||
rightOutput = 0;
|
||||
}
|
||||
}
|
||||
|
||||
leftOutput = std::clamp<int32_t>(leftOutput, INT16_MIN, INT16_MAX);
|
||||
rightOutput = std::clamp<int32_t>(rightOutput, INT16_MIN, INT16_MAX);
|
||||
|
||||
//Use low pass filter and subtract the result to filter out DC offset
|
||||
_soundBuffer[_sampleCount*2] = leftOutput - _filterL.Process(leftOutput);
|
||||
_soundBuffer[_sampleCount*2+1] = rightOutput - _filterR.Process(rightOutput);
|
||||
_sampleCount++;
|
||||
|
||||
if(_sampleCount >= WsApu::MaxSamples) {
|
||||
PlayQueuedAudio();
|
||||
}
|
||||
}
|
||||
|
||||
void WsApu::Run()
|
||||
{
|
||||
_clockCounter++;
|
||||
|
||||
_ch1->Exec();
|
||||
_ch2->Exec();
|
||||
_ch3->Exec();
|
||||
_ch4->Exec();
|
||||
|
||||
uint8_t clock = (_clockCounter - 1) & 0x7F;
|
||||
if(clock >= 116) {
|
||||
switch(clock) {
|
||||
case 116: _dmaController->ProcessSoundDma(); break;
|
||||
case 122: _hyperVoice->Exec(); break;
|
||||
case 123: _ch1->UpdateOutput(); break;
|
||||
case 124: _ch2->UpdateOutput(); break;
|
||||
case 125: _ch3->UpdateOutput(); break;
|
||||
case 126: _ch4->UpdateOutput(); break;
|
||||
case 127: UpdateOutput(); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t WsApu::Read(uint16_t port)
|
||||
{
|
||||
switch(port) {
|
||||
case 0x6A:
|
||||
case 0x6B:
|
||||
return _hyperVoice->Read(port);
|
||||
|
||||
case 0x80: return BitUtilities::GetBits<0>(_state.Ch1.Frequency);
|
||||
case 0x81: return BitUtilities::GetBits<8>(_state.Ch1.Frequency);
|
||||
case 0x82: return BitUtilities::GetBits<0>(_state.Ch2.Frequency);
|
||||
case 0x83: return BitUtilities::GetBits<8>(_state.Ch2.Frequency);
|
||||
case 0x84: return BitUtilities::GetBits<0>(_state.Ch3.Frequency);
|
||||
case 0x85: return BitUtilities::GetBits<8>(_state.Ch3.Frequency);
|
||||
case 0x86: return BitUtilities::GetBits<0>(_state.Ch4.Frequency);
|
||||
case 0x87: return BitUtilities::GetBits<8>(_state.Ch4.Frequency);
|
||||
|
||||
case 0x88: return _state.Ch1.GetVolume();
|
||||
case 0x89: return _state.Ch2.GetVolume();
|
||||
case 0x8A: return _state.Ch3.GetVolume();
|
||||
case 0x8B: return _state.Ch4.GetVolume();
|
||||
|
||||
case 0x8C: return (uint8_t)_state.Ch3.SweepValue;
|
||||
case 0x8D: return _state.Ch3.SweepPeriod;
|
||||
case 0x8E:
|
||||
return (
|
||||
_state.Ch4.TapMode |
|
||||
((uint8_t)_state.Ch4.LfsrEnabled << 4)
|
||||
);
|
||||
|
||||
case 0x8F: return _state.WaveTableAddress >> 6;
|
||||
|
||||
case 0x90:
|
||||
return (
|
||||
(uint8_t)_state.Ch1.Enabled |
|
||||
((uint8_t)_state.Ch2.Enabled << 1) |
|
||||
((uint8_t)_state.Ch3.Enabled << 2) |
|
||||
((uint8_t)_state.Ch4.Enabled << 3) |
|
||||
((uint8_t)_state.Ch2.PcmEnabled << 5) |
|
||||
((uint8_t)_state.Ch3.SweepEnabled << 6) |
|
||||
((uint8_t)_state.Ch4.NoiseEnabled << 7)
|
||||
);
|
||||
|
||||
case 0x91:
|
||||
return (
|
||||
(uint8_t)_state.SpeakerEnabled |
|
||||
(_state.SpeakerVolume << 1) |
|
||||
((uint8_t)_state.HeadphoneEnabled << 3) |
|
||||
(_emu->GetSettings()->GetWsConfig().AudioMode == WsAudioMode::Headphones ? 0x80 : 0)
|
||||
);
|
||||
|
||||
case 0x92: return BitUtilities::GetBits<0>(_state.Ch4.Lfsr);
|
||||
case 0x93: return BitUtilities::GetBits<8>(_state.Ch4.Lfsr);
|
||||
|
||||
case 0x94:
|
||||
return (
|
||||
(uint8_t)_state.Ch2.MaxPcmVolumeRight |
|
||||
((uint8_t)_state.Ch2.HalfPcmVolumeRight << 1) |
|
||||
((uint8_t)_state.Ch2.MaxPcmVolumeLeft << 2) |
|
||||
((uint8_t)_state.Ch2.HalfPcmVolumeLeft << 3)
|
||||
);
|
||||
|
||||
case 0x95:
|
||||
return (
|
||||
(uint8_t)_state.HoldChannels |
|
||||
((uint8_t)_state.Ch3.UseSweepCpuClock << 1) |
|
||||
(_state.Ch4.HoldLfsr << 2) |
|
||||
//todows bit 4?
|
||||
((uint8_t)_state.ForceOutputCh2Voice << 5) |
|
||||
((uint8_t)_state.ForceOutput2 << 6) |
|
||||
((uint8_t)_state.ForceOutput4 << 7)
|
||||
);
|
||||
|
||||
case 0x96: return GetApuOutput(true);
|
||||
case 0x97: return GetApuOutput(true) >> 8;
|
||||
case 0x98: return GetApuOutput(false);
|
||||
case 0x99: return GetApuOutput(false) >> 8;
|
||||
case 0x9A: return GetApuOutput(false) + GetApuOutput(true);
|
||||
case 0x9B: return (GetApuOutput(false) + GetApuOutput(true)) >> 8;
|
||||
|
||||
case 0x9E:
|
||||
if(_console->GetModel() != WsModel::Monochrome) {
|
||||
return _state.MasterVolume;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
//todows open bus
|
||||
return 0x90;
|
||||
}
|
||||
|
||||
void WsApu::Write(uint16_t port, uint8_t value)
|
||||
{
|
||||
switch(port) {
|
||||
case 0x64: case 0x65: case 0x66: case 0x67:
|
||||
case 0x69: case 0x6A: case 0x6B:
|
||||
_hyperVoice->Write(port, value);
|
||||
break;
|
||||
|
||||
case 0x80: BitUtilities::SetBits<0>(_state.Ch1.Frequency, value); break;
|
||||
case 0x81: BitUtilities::SetBits<8>(_state.Ch1.Frequency, value & 0x0F); break;
|
||||
case 0x82: BitUtilities::SetBits<0>(_state.Ch2.Frequency, value); break;
|
||||
case 0x83: BitUtilities::SetBits<8>(_state.Ch2.Frequency, value & 0x0F); break;
|
||||
case 0x84: BitUtilities::SetBits<0>(_state.Ch3.Frequency, value); break;
|
||||
case 0x85: BitUtilities::SetBits<8>(_state.Ch3.Frequency, value & 0x0F); break;
|
||||
case 0x86: BitUtilities::SetBits<0>(_state.Ch4.Frequency, value); break;
|
||||
case 0x87: BitUtilities::SetBits<8>(_state.Ch4.Frequency, value & 0x0F); break;
|
||||
|
||||
case 0x88: _state.Ch1.SetVolume(value); break;
|
||||
case 0x89: _state.Ch2.SetVolume(value); break;
|
||||
case 0x8A: _state.Ch3.SetVolume(value); break;
|
||||
case 0x8B: _state.Ch4.SetVolume(value); break;
|
||||
|
||||
case 0x8C: _state.Ch3.SweepValue = (int8_t)value; break;
|
||||
case 0x8D: _state.Ch3.SweepPeriod = value & 0x1F; break;
|
||||
|
||||
case 0x8E: {
|
||||
static constexpr uint8_t tapShifts[8] = { 14, 10, 13, 4, 8, 6, 9, 11 };
|
||||
_state.Ch4.TapMode = value & 0x07;
|
||||
_state.Ch4.TapShift = tapShifts[_state.Ch4.TapMode];
|
||||
if(value & 0x08) {
|
||||
_state.Ch4.Lfsr = 0;
|
||||
}
|
||||
_state.Ch4.LfsrEnabled = value & 0x10;
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x8F:
|
||||
_state.WaveTableAddress = value << 6;
|
||||
break;
|
||||
|
||||
case 0x90:
|
||||
_state.Ch1.Enabled = value & 0x01;
|
||||
_state.Ch2.Enabled = value & 0x02;
|
||||
_state.Ch3.Enabled = value & 0x04;
|
||||
_state.Ch4.Enabled = value & 0x08;
|
||||
|
||||
_state.Ch2.PcmEnabled = value & 0x20;
|
||||
_state.Ch3.SweepEnabled = value & 0x40;
|
||||
_state.Ch4.NoiseEnabled = value & 0x80;
|
||||
break;
|
||||
|
||||
case 0x91:
|
||||
_state.SpeakerEnabled = value & 0x01;
|
||||
_state.SpeakerVolume = (value & 0x06) >> 1;
|
||||
_state.HeadphoneEnabled = value & 0x08;
|
||||
break;
|
||||
|
||||
case 0x94:
|
||||
_state.Ch2.MaxPcmVolumeRight = value & 0x01;
|
||||
_state.Ch2.HalfPcmVolumeRight = value & 0x02;
|
||||
_state.Ch2.MaxPcmVolumeLeft = value & 0x04;
|
||||
_state.Ch2.HalfPcmVolumeLeft = value & 0x08;
|
||||
break;
|
||||
|
||||
case 0x95:
|
||||
_state.HoldChannels = value & 0x01;
|
||||
_state.Ch3.UseSweepCpuClock = value & 0x02;
|
||||
_state.Ch4.HoldLfsr = (value & 0x0C) >> 2;
|
||||
//todows bit 4?
|
||||
_state.ForceOutputCh2Voice = value & 0x20;
|
||||
_state.ForceOutput2 = value & 0x40;
|
||||
_state.ForceOutput4 = value & 0x80;
|
||||
break;
|
||||
|
||||
case 0x9E:
|
||||
if(_console->GetModel() != WsModel::Monochrome) {
|
||||
_state.InternalMasterVolume = value & 0x03;
|
||||
_state.MasterVolume = value & 0x03;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t WsApu::GetApuOutput(bool forRight)
|
||||
{
|
||||
if(forRight) {
|
||||
return (
|
||||
_state.Ch1.RightOutput +
|
||||
_state.Ch2.RightOutput +
|
||||
_state.Ch3.RightOutput +
|
||||
_state.Ch4.RightOutput
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
_state.Ch1.LeftOutput +
|
||||
_state.Ch2.LeftOutput +
|
||||
_state.Ch3.LeftOutput +
|
||||
_state.Ch4.LeftOutput
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void WsApu::Serialize(Serializer& s)
|
||||
{
|
||||
SV(_state.Ch1.Frequency);
|
||||
SV(_state.Ch1.Timer);
|
||||
SV(_state.Ch1.Enabled);
|
||||
SV(_state.Ch1.LeftVolume);
|
||||
SV(_state.Ch1.RightVolume);
|
||||
SV(_state.Ch1.SamplePosition);
|
||||
SV(_state.Ch1.LeftOutput);
|
||||
SV(_state.Ch1.RightOutput);
|
||||
|
||||
SV(_state.Ch2.Frequency);
|
||||
SV(_state.Ch2.Timer);
|
||||
SV(_state.Ch2.Enabled);
|
||||
SV(_state.Ch2.LeftVolume);
|
||||
SV(_state.Ch2.RightVolume);
|
||||
SV(_state.Ch2.SamplePosition);
|
||||
SV(_state.Ch2.LeftOutput);
|
||||
SV(_state.Ch2.RightOutput);
|
||||
SV(_state.Ch2.PcmEnabled);
|
||||
SV(_state.Ch2.MaxPcmVolumeRight);
|
||||
SV(_state.Ch2.HalfPcmVolumeRight);
|
||||
SV(_state.Ch2.MaxPcmVolumeLeft);
|
||||
SV(_state.Ch2.HalfPcmVolumeLeft);
|
||||
|
||||
SV(_state.Ch3.Frequency);
|
||||
SV(_state.Ch3.Timer);
|
||||
SV(_state.Ch3.Enabled);
|
||||
SV(_state.Ch3.LeftVolume);
|
||||
SV(_state.Ch3.RightVolume);
|
||||
SV(_state.Ch3.SamplePosition);
|
||||
SV(_state.Ch3.LeftOutput);
|
||||
SV(_state.Ch3.RightOutput);
|
||||
SV(_state.Ch3.SweepScaler);
|
||||
SV(_state.Ch3.SweepEnabled);
|
||||
SV(_state.Ch3.SweepValue);
|
||||
SV(_state.Ch3.SweepPeriod);
|
||||
SV(_state.Ch3.SweepTimer);
|
||||
SV(_state.Ch3.UseSweepCpuClock);
|
||||
|
||||
SV(_state.Ch4.Frequency);
|
||||
SV(_state.Ch4.Timer);
|
||||
SV(_state.Ch4.Enabled);
|
||||
SV(_state.Ch4.LeftVolume);
|
||||
SV(_state.Ch4.RightVolume);
|
||||
SV(_state.Ch4.SamplePosition);
|
||||
SV(_state.Ch4.LeftOutput);
|
||||
SV(_state.Ch4.RightOutput);
|
||||
SV(_state.Ch4.NoiseEnabled);
|
||||
SV(_state.Ch4.LfsrEnabled);
|
||||
SV(_state.Ch4.TapMode);
|
||||
SV(_state.Ch4.TapShift);
|
||||
SV(_state.Ch4.Lfsr);
|
||||
SV(_state.Ch4.HoldLfsr);
|
||||
|
||||
SV(_state.Voice.LeftOutput);
|
||||
SV(_state.Voice.RightOutput);
|
||||
SV(_state.Voice.Enabled);
|
||||
SV(_state.Voice.LeftSample);
|
||||
SV(_state.Voice.RightSample);
|
||||
SV(_state.Voice.UpdateRightValue);
|
||||
SV(_state.Voice.Divisor);
|
||||
SV(_state.Voice.Timer);
|
||||
SV(_state.Voice.Input);
|
||||
SV(_state.Voice.Shift);
|
||||
SV(_state.Voice.ChannelMode);
|
||||
SV(_state.Voice.ScalingMode);
|
||||
SV(_state.Voice.ControlLow);
|
||||
SV(_state.Voice.ControlHigh);
|
||||
|
||||
SV(_state.WaveTableAddress);
|
||||
SV(_state.SpeakerEnabled);
|
||||
SV(_state.SpeakerVolume);
|
||||
SV(_state.MasterVolume);
|
||||
SV(_state.InternalMasterVolume);
|
||||
SV(_state.HeadphoneEnabled);
|
||||
SV(_state.HoldChannels);
|
||||
SV(_state.ForceOutput2);
|
||||
SV(_state.ForceOutput4);
|
||||
SV(_state.ForceOutputCh2Voice);
|
||||
|
||||
SV(_filterL);
|
||||
SV(_filterR);
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
#include "pch.h"
|
||||
#include "WS/WsTypes.h"
|
||||
#include "Utilities/ISerializable.h"
|
||||
#include "Utilities/Audio/OnePoleLowPassFilter.h"
|
||||
|
||||
class Emulator;
|
||||
class WsConsole;
|
||||
class WsMemoryManager;
|
||||
class WsDmaController;
|
||||
class WsApuCh1;
|
||||
class WsApuCh2;
|
||||
class WsApuCh3;
|
||||
class WsApuCh4;
|
||||
class WsHyperVoice;
|
||||
class SoundMixer;
|
||||
|
||||
class WsApu final : public ISerializable
|
||||
{
|
||||
private:
|
||||
static constexpr int ApuFrequency = 24000;
|
||||
static constexpr int MaxSamples = 256;
|
||||
|
||||
private:
|
||||
WsApuState _state = {};
|
||||
|
||||
Emulator* _emu = nullptr;
|
||||
WsConsole* _console = nullptr;
|
||||
WsMemoryManager* _memoryManager = nullptr;
|
||||
WsDmaController* _dmaController = nullptr;
|
||||
SoundMixer* _soundMixer = nullptr;
|
||||
|
||||
unique_ptr<WsApuCh1> _ch1;
|
||||
unique_ptr<WsApuCh2> _ch2;
|
||||
unique_ptr<WsApuCh3> _ch3;
|
||||
unique_ptr<WsApuCh4> _ch4;
|
||||
unique_ptr<WsHyperVoice> _hyperVoice;
|
||||
|
||||
OnePoleLowPassFilter _filterL;
|
||||
OnePoleLowPassFilter _filterR;
|
||||
|
||||
int16_t* _soundBuffer = nullptr;
|
||||
|
||||
uint32_t _clockCounter = 0;
|
||||
uint16_t _sampleCount = 0;
|
||||
|
||||
void UpdateOutput();
|
||||
uint16_t GetApuOutput(bool forRight);
|
||||
|
||||
public:
|
||||
WsApu(Emulator* emu, WsConsole* console, WsMemoryManager* memoryManager, WsDmaController* dmaController);
|
||||
~WsApu();
|
||||
|
||||
void ChangeMasterVolume();
|
||||
|
||||
void PlayQueuedAudio();
|
||||
void WriteDma(bool forHyperVoice, uint8_t sampleValue);
|
||||
|
||||
WsApuState& GetState() { return _state; }
|
||||
|
||||
uint8_t GetMasterVolume() { return _state.InternalMasterVolume; }
|
||||
|
||||
uint8_t ReadSample(uint8_t ch, uint8_t pos);
|
||||
void Run();
|
||||
|
||||
uint8_t Read(uint16_t port);
|
||||
void Write(uint16_t port, uint8_t value);
|
||||
|
||||
void Serialize(Serializer& s) override;
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
#include "pch.h"
|
||||
#include "WS/APU/WsApu.h"
|
||||
#include "WS/WsTypes.h"
|
||||
|
||||
class WsApuCh1
|
||||
{
|
||||
private:
|
||||
WsApu* _apu = nullptr;
|
||||
WsApuCh1State* _state = nullptr;
|
||||
|
||||
public:
|
||||
WsApuCh1(WsApu* apu, WsApuCh1State& state)
|
||||
{
|
||||
_state = &state;
|
||||
_apu = apu;
|
||||
}
|
||||
|
||||
void Exec()
|
||||
{
|
||||
if(!_state->Enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(_state->Timer == 0) {
|
||||
_state->Timer = 2047 - _state->Frequency;
|
||||
_state->SamplePosition = (_state->SamplePosition + 1) & 0x1F;
|
||||
} else {
|
||||
_state->Timer--;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateOutput()
|
||||
{
|
||||
if(!_state->Enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t sample = _apu->ReadSample(0, _state->SamplePosition);
|
||||
|
||||
_state->LeftOutput = _state->LeftVolume * sample;
|
||||
_state->RightOutput = _state->RightVolume * sample;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
#include "pch.h"
|
||||
#include "WS/APU/WsApu.h"
|
||||
#include "WS/WsTypes.h"
|
||||
|
||||
class WsApuCh2
|
||||
{
|
||||
private:
|
||||
WsApu* _apu = nullptr;
|
||||
WsApuCh2State* _state = nullptr;
|
||||
|
||||
public:
|
||||
WsApuCh2(WsApu* apu, WsApuCh2State& state)
|
||||
{
|
||||
_state = &state;
|
||||
_apu = apu;
|
||||
}
|
||||
|
||||
void Exec()
|
||||
{
|
||||
if(!_state->Enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(_state->Timer == 0) {
|
||||
_state->Timer = 2047 - _state->Frequency;
|
||||
_state->SamplePosition = (_state->SamplePosition + 1) & 0x1F;
|
||||
} else {
|
||||
_state->Timer--;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateOutput()
|
||||
{
|
||||
if(!_state->Enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(_state->PcmEnabled) {
|
||||
uint8_t sample = _state->GetVolume();
|
||||
_state->LeftOutput = sample >> (_state->MaxPcmVolumeLeft ? 0 : (_state->HalfPcmVolumeLeft ? 1 : 8));
|
||||
_state->RightOutput = sample >> (_state->MaxPcmVolumeRight ? 0 : (_state->HalfPcmVolumeRight ? 1 : 8));
|
||||
} else {
|
||||
uint8_t sample = _apu->ReadSample(1, _state->SamplePosition);
|
||||
_state->LeftOutput = _state->LeftVolume * sample;
|
||||
_state->RightOutput = _state->RightVolume * sample;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
#include "pch.h"
|
||||
#include "WS/APU/WsApu.h"
|
||||
#include "WS/WsTypes.h"
|
||||
|
||||
class WsApuCh3
|
||||
{
|
||||
private:
|
||||
WsApu* _apu = nullptr;
|
||||
WsApuCh3State* _state = nullptr;
|
||||
|
||||
public:
|
||||
WsApuCh3(WsApu* apu, WsApuCh3State& state)
|
||||
{
|
||||
_state = &state;
|
||||
_apu = apu;
|
||||
}
|
||||
|
||||
void Exec()
|
||||
{
|
||||
if(!_state->Enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(_state->SweepEnabled) {
|
||||
_state->SweepScaler++;
|
||||
if(_state->UseSweepCpuClock || _state->SweepScaler >= 0x2000) {
|
||||
_state->SweepScaler = 0;
|
||||
|
||||
if(_state->SweepTimer == 0) {
|
||||
_state->SweepTimer = _state->SweepPeriod;
|
||||
_state->Frequency = (_state->Frequency + _state->SweepValue) & 0xFFF;
|
||||
} else {
|
||||
_state->SweepTimer--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(_state->Timer == 0) {
|
||||
_state->Timer = 2047 - _state->Frequency;
|
||||
_state->SamplePosition = (_state->SamplePosition + 1) & 0x1F;
|
||||
} else {
|
||||
_state->Timer--;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateOutput()
|
||||
{
|
||||
if(!_state->Enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t sample = _apu->ReadSample(2, _state->SamplePosition);
|
||||
|
||||
_state->LeftOutput = _state->LeftVolume * sample;
|
||||
_state->RightOutput = _state->RightVolume * sample;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
#include "pch.h"
|
||||
#include "WS/APU/WsApu.h"
|
||||
#include "WS/WsTypes.h"
|
||||
|
||||
class WsApuCh4
|
||||
{
|
||||
private:
|
||||
WsApu* _apu = nullptr;
|
||||
WsApuCh4State* _state = nullptr;
|
||||
|
||||
public:
|
||||
WsApuCh4(WsApu* apu, WsApuCh4State& state)
|
||||
{
|
||||
_state = &state;
|
||||
_apu = apu;
|
||||
}
|
||||
|
||||
void Exec()
|
||||
{
|
||||
if(!_state->Enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(_state->Timer == 0) {
|
||||
_state->Timer = 2047 - _state->Frequency;
|
||||
_state->SamplePosition = (_state->SamplePosition + 1) & 0x1F; //todows does this happen even when lfsr is enabled?
|
||||
|
||||
if(_state->LfsrEnabled) {
|
||||
uint8_t newBit = ((_state->Lfsr >> 7) ^ (_state->Lfsr >> _state->TapShift) ^ 0x01) & 0x01;
|
||||
_state->Lfsr = ((_state->Lfsr << 1) | newBit) & 0x7FFF;
|
||||
}
|
||||
} else {
|
||||
_state->Timer--;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateOutput()
|
||||
{
|
||||
if(!_state->Enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t sample;
|
||||
if(_state->NoiseEnabled) {
|
||||
sample = (_state->Lfsr & 0x01) ? 0x0F : 0;
|
||||
} else {
|
||||
sample = _apu->ReadSample(3, _state->SamplePosition);
|
||||
}
|
||||
|
||||
_state->LeftOutput = _state->LeftVolume * sample;
|
||||
_state->RightOutput = _state->RightVolume * sample;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,127 @@
|
||||
#pragma once
|
||||
#include "pch.h"
|
||||
#include "WS/WsTypes.h"
|
||||
#include "Utilities/BitUtilities.h"
|
||||
|
||||
class WsHyperVoice
|
||||
{
|
||||
private:
|
||||
WsApuHyperVoiceState* _state = nullptr;
|
||||
|
||||
public:
|
||||
WsHyperVoice(WsApuHyperVoiceState& state)
|
||||
{
|
||||
_state = &state;
|
||||
}
|
||||
|
||||
int16_t ConvertSampleToOutput(uint8_t sample)
|
||||
{
|
||||
int32_t output = 0;
|
||||
switch(_state->ScalingMode) {
|
||||
case WsHyperVoiceScalingMode::Unsigned: output = sample << 8; break;
|
||||
case WsHyperVoiceScalingMode::UnsignedNegated: output = 0xFFFF0000 | (sample << 8); break;
|
||||
case WsHyperVoiceScalingMode::Signed: output = ((int32_t)(int8_t)sample) << 8; break;
|
||||
case WsHyperVoiceScalingMode::None: output = (int16_t)(sample << 8);
|
||||
}
|
||||
|
||||
if(_state->ScalingMode != WsHyperVoiceScalingMode::None) {
|
||||
output >>= _state->Shift;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
void Exec()
|
||||
{
|
||||
if(!_state->Enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(_state->Timer == 0) {
|
||||
_state->Timer = _state->Divisor;
|
||||
|
||||
_state->LeftOutput = ConvertSampleToOutput(_state->LeftSample);
|
||||
_state->RightOutput = ConvertSampleToOutput(_state->RightSample);
|
||||
} else {
|
||||
_state->Timer--;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t Read(uint16_t port)
|
||||
{
|
||||
switch(port) {
|
||||
case 0x6A: return _state->ControlLow;
|
||||
case 0x6B: return _state->ControlHigh;
|
||||
}
|
||||
|
||||
//TODOWS open bus
|
||||
return 0x90;
|
||||
}
|
||||
|
||||
void WriteStereoInput(uint8_t sample)
|
||||
{
|
||||
if(_state->UpdateRightValue) {
|
||||
_state->RightSample = sample;
|
||||
} else {
|
||||
_state->LeftSample = sample;
|
||||
}
|
||||
_state->UpdateRightValue = !_state->UpdateRightValue;
|
||||
}
|
||||
|
||||
void WriteDma(uint8_t sample)
|
||||
{
|
||||
switch(_state->ChannelMode) {
|
||||
case WsHyperVoiceChannelMode::Stereo:
|
||||
WriteStereoInput(sample);
|
||||
break;
|
||||
|
||||
case WsHyperVoiceChannelMode::MonoLeft:
|
||||
_state->LeftSample = sample;
|
||||
break;
|
||||
|
||||
case WsHyperVoiceChannelMode::MonoRight:
|
||||
_state->RightSample = sample;
|
||||
break;
|
||||
|
||||
case WsHyperVoiceChannelMode::MonoBoth:
|
||||
_state->LeftSample = sample;
|
||||
_state->RightSample = sample;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Write(uint16_t port, uint8_t value)
|
||||
{
|
||||
switch(port) {
|
||||
case 0x64: BitUtilities::SetBits<0>(_state->LeftOutput, value); break;
|
||||
case 0x65: BitUtilities::SetBits<8>(_state->LeftOutput, value); break;
|
||||
case 0x66: BitUtilities::SetBits<0>(_state->RightOutput, value); break;
|
||||
case 0x67: BitUtilities::SetBits<8>(_state->RightOutput, value); break;
|
||||
|
||||
case 0x69: WriteStereoInput(value); break;
|
||||
|
||||
case 0x6A: {
|
||||
_state->Shift = value & 0x03;
|
||||
_state->ScalingMode = (WsHyperVoiceScalingMode)((value >> 2) & 0x03);
|
||||
_state->Enabled = value & 0x80;
|
||||
uint8_t dividor = (value >> 4) & 0x07;
|
||||
switch(dividor) {
|
||||
default: _state->Divisor = dividor; break;
|
||||
case 6: _state->Divisor = 7; break;
|
||||
case 7: _state->Divisor = 11; break;
|
||||
}
|
||||
_state->ControlLow = value;
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x6B: {
|
||||
bool reset = (value & 0x10);
|
||||
if(reset) {
|
||||
_state->UpdateRightValue = false;
|
||||
}
|
||||
_state->ChannelMode = (WsHyperVoiceChannelMode)((value >> 5) & 0x03);
|
||||
_state->ControlHigh = value & 0x6F;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,64 @@
|
||||
#include "pch.h"
|
||||
#include "WS/Carts/WsCart.h"
|
||||
#include "WS/WsMemoryManager.h"
|
||||
#include "WS/WsEeprom.h"
|
||||
#include "Utilities/Serializer.h"
|
||||
|
||||
void WsCart::Map(uint32_t start, uint32_t end, MemoryType type, uint32_t offset, bool readonly)
|
||||
{
|
||||
_memoryManager->Map(start, end, type, offset, readonly);
|
||||
}
|
||||
|
||||
void WsCart::Unmap(uint32_t start, uint32_t end)
|
||||
{
|
||||
_memoryManager->Unmap(start, end);
|
||||
}
|
||||
|
||||
WsCart::WsCart()
|
||||
{
|
||||
_state.SelectedBanks[0] = 0xFF;
|
||||
_state.SelectedBanks[1] = 0xFF;
|
||||
_state.SelectedBanks[2] = 0xFF;
|
||||
_state.SelectedBanks[3] = 0xFF;
|
||||
}
|
||||
|
||||
void WsCart::Init(WsMemoryManager* memoryManager, WsEeprom* cartEeprom)
|
||||
{
|
||||
_memoryManager = memoryManager;
|
||||
_cartEeprom = cartEeprom;
|
||||
}
|
||||
|
||||
void WsCart::RefreshMappings()
|
||||
{
|
||||
Map(0x10000, 0x1FFFF, MemoryType::WsCartRam, _state.SelectedBanks[1] * 0x10000, false);
|
||||
Map(0x20000, 0x2FFFF, MemoryType::WsPrgRom, _state.SelectedBanks[2] * 0x10000, true);
|
||||
Map(0x30000, 0x3FFFF, MemoryType::WsPrgRom, _state.SelectedBanks[3] * 0x10000, true);
|
||||
Map(0x40000, 0xFFFFF, MemoryType::WsPrgRom, _state.SelectedBanks[0] * 0x100000 + 0x40000, true);
|
||||
}
|
||||
|
||||
uint8_t WsCart::ReadPort(uint16_t port)
|
||||
{
|
||||
if(port < 0xC4) {
|
||||
return _state.SelectedBanks[port - 0xC0];
|
||||
} else if(port < 0xC9 && _cartEeprom) {
|
||||
return _cartEeprom->ReadPort(port - 0xC4);
|
||||
}
|
||||
|
||||
//TODOWS open bus
|
||||
return 0x90;
|
||||
}
|
||||
|
||||
void WsCart::WritePort(uint16_t port, uint8_t value)
|
||||
{
|
||||
if(port < 0xC4) {
|
||||
_state.SelectedBanks[port - 0xC0] = value;
|
||||
_memoryManager->RefreshMappings();
|
||||
} else if(port < 0xC9 && _cartEeprom) {
|
||||
_cartEeprom->WritePort(port - 0xC4, value);
|
||||
}
|
||||
}
|
||||
|
||||
void WsCart::Serialize(Serializer& s)
|
||||
{
|
||||
SVArray(_state.SelectedBanks, 4);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
#include "pch.h"
|
||||
#include "WS/WsTypes.h"
|
||||
#include "Utilities/ISerializable.h"
|
||||
#include "Shared/MemoryType.h"
|
||||
|
||||
class WsMemoryManager;
|
||||
class WsEeprom;
|
||||
|
||||
//TODOWS RTC
|
||||
//TODOWS Flash
|
||||
|
||||
class WsCart final: public ISerializable
|
||||
{
|
||||
protected:
|
||||
WsCartState _state = {};
|
||||
|
||||
WsMemoryManager* _memoryManager = nullptr;
|
||||
WsEeprom* _cartEeprom = nullptr;
|
||||
|
||||
void Map(uint32_t start, uint32_t end, MemoryType type, uint32_t offset, bool readonly);
|
||||
void Unmap(uint32_t start, uint32_t end);
|
||||
|
||||
public:
|
||||
WsCart();
|
||||
virtual ~WsCart() {}
|
||||
|
||||
void Init(WsMemoryManager* memoryManager, WsEeprom* cartEeprom);
|
||||
void RefreshMappings();
|
||||
|
||||
WsCartState& GetState() { return _state; }
|
||||
|
||||
virtual uint8_t ReadPort(uint16_t port);
|
||||
virtual void WritePort(uint16_t port, uint8_t value);
|
||||
|
||||
void Serialize(Serializer& s) override;
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
#include "pch.h"
|
||||
#include "WS/Debugger/DummyWsCpu.h"
|
||||
|
||||
#define DUMMYCPU
|
||||
#define WsCpu DummyWsCpu
|
||||
#include "WS/WsCpu.cpp"
|
||||
#undef WsCpu
|
||||
#undef DUMMYCPU
|
||||
|
||||
void DummyWsCpu::SetDummyState(WsCpuState& state)
|
||||
{
|
||||
_memOpCounter = 0;
|
||||
_state = state;
|
||||
_state.Halted = false;
|
||||
_state.Flags.Trap = false;
|
||||
}
|
||||
|
||||
uint32_t DummyWsCpu::GetOperationCount()
|
||||
{
|
||||
return _memOpCounter;
|
||||
}
|
||||
|
||||
void DummyWsCpu::LogMemoryOperation(uint32_t addr, uint16_t value, MemoryOperationType type, MemoryType memType, bool isWordAccess)
|
||||
{
|
||||
if(_memOpCounter < 32) {
|
||||
_memOperations[_memOpCounter] = {
|
||||
addr,
|
||||
(int32_t)value,
|
||||
type,
|
||||
memType
|
||||
};
|
||||
_memWordAccess[_memOpCounter] = isWordAccess;
|
||||
_memOpCounter++;
|
||||
}
|
||||
}
|
||||
|
||||
MemoryOperationInfo DummyWsCpu::GetOperationInfo(uint32_t index)
|
||||
{
|
||||
return _memOperations[index];
|
||||
}
|
||||
|
||||
bool DummyWsCpu::IsWordAccess(uint32_t index)
|
||||
{
|
||||
return _memWordAccess[index];
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "pch.h"
|
||||
#include "Debugger/DebugTypes.h"
|
||||
|
||||
#define DUMMYCPU
|
||||
#define WsCpu DummyWsCpu
|
||||
#include "WS/WsCpu.h"
|
||||
#undef WsCpu
|
||||
#undef DUMMYCPU
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
#include "pch.h"
|
||||
#include "Debugger/IAssembler.h"
|
||||
|
||||
class LabelManager;
|
||||
|
||||
class WsAssembler final : public IAssembler
|
||||
{
|
||||
private:
|
||||
LabelManager* _labelManager;
|
||||
|
||||
public:
|
||||
WsAssembler(LabelManager* labelManager)
|
||||
{
|
||||
_labelManager = labelManager;
|
||||
}
|
||||
|
||||
virtual ~WsAssembler() {}
|
||||
|
||||
uint32_t AssembleCode(string code, uint32_t startAddress, int16_t* assembledCode)
|
||||
{
|
||||
//TODOWS
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,513 @@
|
||||
#include "pch.h"
|
||||
#include "WS/Debugger/DummyWsCpu.h"
|
||||
#include "WS/Debugger/WsDebugger.h"
|
||||
#include "WS/Debugger/WsAssembler.h"
|
||||
#include "WS/Debugger/WsDisUtils.h"
|
||||
#include "WS/Debugger/WsEventManager.h"
|
||||
#include "WS/Debugger/WsTraceLogger.h"
|
||||
#include "WS/Debugger/WsPpuTools.h"
|
||||
#include "WS/WsController.h"
|
||||
#include "WS/WsCpu.h"
|
||||
#include "WS/WsPpu.h"
|
||||
#include "WS/WsConsole.h"
|
||||
#include "WS/WsMemoryManager.h"
|
||||
#include "Debugger/DisassemblyInfo.h"
|
||||
#include "Debugger/Disassembler.h"
|
||||
#include "Debugger/MemoryDumper.h"
|
||||
#include "Debugger/CallstackManager.h"
|
||||
#include "Debugger/BreakpointManager.h"
|
||||
#include "Debugger/Debugger.h"
|
||||
#include "Debugger/MemoryAccessCounter.h"
|
||||
#include "Debugger/ExpressionEvaluator.h"
|
||||
#include "Debugger/MemoryDumper.h"
|
||||
#include "Debugger/CodeDataLogger.h"
|
||||
#include "Debugger/BaseEventManager.h"
|
||||
#include "Debugger/StepBackManager.h"
|
||||
#include "Utilities/Patches/IpsPatcher.h"
|
||||
#include "Utilities/HexUtilities.h"
|
||||
#include "Shared/EmuSettings.h"
|
||||
#include "Shared/Emulator.h"
|
||||
#include "Shared/BaseControlManager.h"
|
||||
#include "Shared/MemoryOperationType.h"
|
||||
|
||||
WsDebugger::WsDebugger(Debugger* debugger) : IDebugger(debugger->GetEmulator())
|
||||
{
|
||||
_debugger = debugger;
|
||||
_memoryDumper = debugger->GetMemoryDumper();
|
||||
_emu = debugger->GetEmulator();
|
||||
|
||||
_disassembler = debugger->GetDisassembler();
|
||||
_memoryAccessCounter = debugger->GetMemoryAccessCounter();
|
||||
|
||||
_console = ((WsConsole*)debugger->GetConsole());
|
||||
|
||||
_cpu = _console->GetCpu();
|
||||
_ppu = _console->GetPpu();
|
||||
_memoryManager = _console->GetMemoryManager();
|
||||
|
||||
_settings = debugger->GetEmulator()->GetSettings();
|
||||
|
||||
_codeDataLogger.reset(new CodeDataLogger(debugger, MemoryType::WsPrgRom, _emu->GetMemory(MemoryType::WsPrgRom).Size, CpuType::Ws, _emu->GetCrc32()));
|
||||
_cdlFile = _codeDataLogger->GetCdlFilePath(_emu->GetRomInfo().RomFile.GetFileName());
|
||||
_codeDataLogger->LoadCdlFile(_cdlFile, _settings->GetDebugConfig().AutoResetCdl);
|
||||
|
||||
_traceLogger.reset(new WsTraceLogger(debugger, this, _ppu));
|
||||
_ppuTools.reset(new WsPpuTools(debugger, debugger->GetEmulator(), _console));
|
||||
|
||||
_stepBackManager.reset(new StepBackManager(_emu, this));
|
||||
_eventManager.reset(new WsEventManager(debugger, _console, _cpu, _ppu));
|
||||
_callstackManager.reset(new CallstackManager(debugger, this));
|
||||
_breakpointManager.reset(new BreakpointManager(debugger, this, CpuType::Ws, _eventManager.get()));
|
||||
_step.reset(new StepRequest());
|
||||
_assembler.reset(new WsAssembler(debugger->GetLabelManager()));
|
||||
|
||||
_dummyCpu.reset(new DummyWsCpu(_emu, _memoryManager));
|
||||
|
||||
ResetPrevOpCode();
|
||||
}
|
||||
|
||||
WsDebugger::~WsDebugger()
|
||||
{
|
||||
_codeDataLogger->SaveCdlFile(_cdlFile);
|
||||
}
|
||||
|
||||
void WsDebugger::Reset()
|
||||
{
|
||||
_callstackManager->Clear();
|
||||
ResetPrevOpCode();
|
||||
}
|
||||
|
||||
void WsDebugger::ProcessInstruction()
|
||||
{
|
||||
WsCpuState& state = _cpu->GetState();
|
||||
uint32_t pc = _cpu->GetProgramCounter(true);
|
||||
AddressInfo addressInfo = _console->GetAbsoluteAddress(pc);
|
||||
uint8_t value = _memoryManager->DebugRead(pc);
|
||||
MemoryOperationInfo operation(pc, value, MemoryOperationType::ExecOpCode, MemoryType::WsMemory);
|
||||
InstructionProgress.LastMemOperation = operation;
|
||||
InstructionProgress.StartCycle = state.CycleCount;
|
||||
|
||||
if(addressInfo.Address >= 0) {
|
||||
if(addressInfo.Type == MemoryType::WsPrgRom) {
|
||||
if(WsDisUtils::IsConditionalJump(_prevOpCode)) {
|
||||
uint8_t opSize = WsDisUtils::GetOpSize(_prevProgramCounter, MemoryType::WsMemory, _memoryDumper);
|
||||
_codeDataLogger->SetCode(addressInfo.Address, WsDisUtils::GetOpFlags(_prevOpCode, pc, _prevProgramCounter, opSize));
|
||||
} else {
|
||||
_codeDataLogger->SetCode(addressInfo.Address, WsDisUtils::GetOpFlags(_prevOpCode, pc, _prevProgramCounter, 0));
|
||||
}
|
||||
}
|
||||
_disassembler->BuildCache(addressInfo, 0, CpuType::Ws);
|
||||
}
|
||||
|
||||
ProcessCallStackUpdates(addressInfo, pc);
|
||||
|
||||
if(_step->BreakAddress == (int32_t)pc) {
|
||||
//If we're on the expected return address, break immediately (for step over/step out)
|
||||
if(WsDisUtils::IsReturnInstruction(_prevOpCode)) {
|
||||
_step->Break(BreakSource::CpuStep);
|
||||
} else {
|
||||
uint8_t prefix = _memoryManager->DebugRead(_prevProgramCounter);
|
||||
if(prefix == 0xF2 || prefix == 0xF3) {
|
||||
//REPZ/REPNZ prefix
|
||||
_step->Break(BreakSource::CpuStep);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_prevOpCode = WsDisUtils::GetFullOpCode(state.CS, state.IP, _memoryManager);
|
||||
_prevProgramCounter = pc;
|
||||
|
||||
if(_settings->CheckDebuggerFlag(DebuggerFlags::WsDebuggerEnabled)) {
|
||||
if(_settings->GetDebugConfig().WsBreakOnInvalidOpCode && WsDisUtils::IsUndefinedOpCode(_prevOpCode)) {
|
||||
_step->Break(BreakSource::BreakOnUndefinedOpCode);
|
||||
}
|
||||
}
|
||||
|
||||
_step->ProcessCpuExec();
|
||||
|
||||
if(_step->StepCount != 0 && _breakpointManager->HasBreakpoints() && _settings->GetDebugConfig().UsePredictiveBreakpoints) {
|
||||
_dummyCpu->SetDummyState(state);
|
||||
_dummyCpu->Exec();
|
||||
for(uint32_t i = 1; i < _dummyCpu->GetOperationCount(); i++) {
|
||||
MemoryOperationInfo memOp = _dummyCpu->GetOperationInfo(i);
|
||||
if(_breakpointManager->HasBreakpointForType(memOp.Type)) {
|
||||
AddressInfo absAddr = _console->GetAbsoluteAddress(memOp.Address);
|
||||
_debugger->ProcessPredictiveBreakpoint(CpuType::Ws, _breakpointManager.get(), memOp, absAddr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_debugger->ProcessBreakConditions(CpuType::Ws, *_step.get(), _breakpointManager.get(), operation, addressInfo);
|
||||
}
|
||||
|
||||
template<uint8_t accessWidth>
|
||||
void WsDebugger::ProcessRead(uint32_t addr, uint16_t value, MemoryOperationType type)
|
||||
{
|
||||
AddressInfo addressInfo = _console->GetAbsoluteAddress(addr);
|
||||
MemoryOperationInfo operation(addr, value, type, MemoryType::WsMemory);
|
||||
InstructionProgress.LastMemOperation = operation;
|
||||
|
||||
if(type == MemoryOperationType::ExecOpCode) {
|
||||
if(_traceLogger->IsEnabled()) {
|
||||
DisassemblyInfo disInfo = _disassembler->GetDisassemblyInfo(addressInfo, addr, 0, CpuType::Ws);
|
||||
_traceLogger->Log(_cpu->GetState(), disInfo, operation, addressInfo);
|
||||
}
|
||||
_memoryAccessCounter->ProcessMemoryExec(addressInfo, _console->GetMasterClock());
|
||||
if(_step->ProcessCpuCycle()) {
|
||||
_debugger->SleepUntilResume(CpuType::Ws, BreakSource::CpuStep, &operation);
|
||||
}
|
||||
} else if(type == MemoryOperationType::ExecOperand) {
|
||||
if(addressInfo.Address >= 0 && addressInfo.Type == MemoryType::WsPrgRom) {
|
||||
_codeDataLogger->SetCode(addressInfo.Address);
|
||||
}
|
||||
|
||||
if(_traceLogger->IsEnabled()) {
|
||||
_traceLogger->LogNonExec(operation, addressInfo);
|
||||
}
|
||||
|
||||
_memoryAccessCounter->ProcessMemoryExec<accessWidth>(addressInfo, _console->GetMasterClock());
|
||||
_step->ProcessCpuCycle();
|
||||
_debugger->ProcessBreakConditions(CpuType::Ws, *_step.get(), _breakpointManager.get(), operation, addressInfo);
|
||||
} else {
|
||||
if(addressInfo.Address >= 0 && addressInfo.Type == MemoryType::WsPrgRom) {
|
||||
_codeDataLogger->SetData<0, accessWidth>(addressInfo.Address);
|
||||
}
|
||||
|
||||
if(_traceLogger->IsEnabled()) {
|
||||
_traceLogger->LogNonExec(operation, addressInfo);
|
||||
}
|
||||
|
||||
if(addr < 0xFE00 || addr >= 0xFF80) {
|
||||
ReadResult result = _memoryAccessCounter->ProcessMemoryRead<accessWidth>(addressInfo, _console->GetMasterClock());
|
||||
if(result != ReadResult::Normal) {
|
||||
//Memory access was a read on an uninitialized memory address
|
||||
if(result == ReadResult::FirstUninitRead) {
|
||||
//Only warn the first time
|
||||
_debugger->Log("[WS] Uninitialized memory read: $" + HexUtilities::ToHex20(addr));
|
||||
}
|
||||
if(_settings->CheckDebuggerFlag(DebuggerFlags::WsDebuggerEnabled) && _settings->GetDebugConfig().BreakOnUninitRead) {
|
||||
_step->Break(BreakSource::BreakOnUninitMemoryRead);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_step->ProcessCpuCycle();
|
||||
_debugger->ProcessBreakConditions<accessWidth>(CpuType::Ws, *_step.get(), _breakpointManager.get(), operation, addressInfo);
|
||||
}
|
||||
}
|
||||
|
||||
template<uint8_t accessWidth>
|
||||
void WsDebugger::ProcessWrite(uint32_t addr, uint16_t value, MemoryOperationType type)
|
||||
{
|
||||
AddressInfo addressInfo = _console->GetAbsoluteAddress(addr);
|
||||
MemoryOperationInfo operation(addr, value, type, MemoryType::WsMemory);
|
||||
InstructionProgress.LastMemOperation = operation;
|
||||
|
||||
if(addressInfo.Type == MemoryType::WsWorkRam || addressInfo.Type == MemoryType::WsCartRam) {
|
||||
if(addressInfo.Type == MemoryType::WsWorkRam) {
|
||||
bool isMono = _ppu->GetState().Mode == WsVideoMode::Monochrome;
|
||||
if(isMono && addressInfo.Address >= 0x2000 && addressInfo.Address <= 0x3FFF) {
|
||||
_eventManager->AddEvent(DebugEventType::Register, operation);
|
||||
} else if(!isMono && (addressInfo.Address >= 0xFE00 || (addressInfo.Address >= 0x4000 && addressInfo.Address <= 0xBFFF))) {
|
||||
_eventManager->AddEvent(DebugEventType::Register, operation);
|
||||
}
|
||||
}
|
||||
|
||||
_disassembler->InvalidateCache(addressInfo, CpuType::Ws);
|
||||
}
|
||||
|
||||
if(_traceLogger->IsEnabled()) {
|
||||
_traceLogger->LogNonExec(operation, addressInfo);
|
||||
}
|
||||
|
||||
_memoryAccessCounter->ProcessMemoryWrite<accessWidth>(addressInfo, _console->GetMasterClock());
|
||||
_step->ProcessCpuCycle();
|
||||
_debugger->ProcessBreakConditions<accessWidth>(CpuType::Ws, *_step.get(), _breakpointManager.get(), operation, addressInfo);
|
||||
}
|
||||
|
||||
template<MemoryOperationType opType, typename T>
|
||||
void WsDebugger::ProcessMemoryAccess(uint32_t addr, T value, MemoryType memType)
|
||||
{
|
||||
MemoryOperationInfo operation(addr, value, opType, memType);
|
||||
_eventManager->AddEvent(DebugEventType::Register, operation);
|
||||
}
|
||||
|
||||
void WsDebugger::Run()
|
||||
{
|
||||
_step.reset(new StepRequest());
|
||||
}
|
||||
|
||||
void WsDebugger::Step(int32_t stepCount, StepType type)
|
||||
{
|
||||
StepRequest step(type);
|
||||
|
||||
switch(type) {
|
||||
case StepType::Step: step.StepCount = stepCount; break;
|
||||
case StepType::StepOut: step.BreakAddress = _callstackManager->GetReturnAddress(); break;
|
||||
case StepType::StepOver:
|
||||
if(WsDisUtils::IsJumpToSub(_prevOpCode)) {
|
||||
step.BreakAddress = _prevProgramCounter + GetPrevOpCodeSize();
|
||||
} else {
|
||||
uint8_t prefix = _memoryManager->DebugRead(_prevProgramCounter);
|
||||
if(prefix == 0xF2 || prefix == 0xF3) {
|
||||
//REPZ/REPNZ prefixed instruction
|
||||
step.BreakAddress = _prevProgramCounter + GetPrevOpCodeSize();
|
||||
} else {
|
||||
//For any other instruction, step over is the same as step into
|
||||
step.StepCount = 1;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case StepType::CpuCycleStep: step.CpuCycleStepCount = stepCount; break;
|
||||
case StepType::PpuStep: step.PpuStepCount = stepCount; break;
|
||||
case StepType::PpuScanline: step.PpuStepCount = 256 * stepCount; break;
|
||||
case StepType::PpuFrame: step.PpuStepCount = 256 * _ppu->GetScanlineCount() * stepCount; break;
|
||||
case StepType::SpecificScanline: step.BreakScanline = stepCount; break;
|
||||
}
|
||||
|
||||
_step.reset(new StepRequest(step));
|
||||
}
|
||||
|
||||
StepBackConfig WsDebugger::GetStepBackConfig()
|
||||
{
|
||||
return {
|
||||
_cpu->GetCycleCount(),
|
||||
256,
|
||||
256u * _ppu->GetScanlineCount()
|
||||
};
|
||||
}
|
||||
|
||||
void WsDebugger::DrawPartialFrame()
|
||||
{
|
||||
_ppu->DebugSendFrame();
|
||||
}
|
||||
|
||||
uint8_t WsDebugger::GetPrevOpCodeSize()
|
||||
{
|
||||
return WsDisUtils::GetOpSize(_prevProgramCounter, MemoryType::WsMemory, _debugger->GetMemoryDumper());
|
||||
}
|
||||
|
||||
void WsDebugger::ProcessCallStackUpdates(AddressInfo& destAddr, uint32_t destPc)
|
||||
{
|
||||
if(WsDisUtils::IsJumpToSub(_prevOpCode) && destPc != _prevProgramCounter + GetPrevOpCodeSize()) {
|
||||
//CALL and RST, and PC doesn't match the next instruction, so the call was (probably) done
|
||||
uint8_t opSize = GetPrevOpCodeSize();
|
||||
uint32_t returnPc = _prevProgramCounter + opSize;
|
||||
AddressInfo src = _console->GetAbsoluteAddress(_prevProgramCounter);
|
||||
AddressInfo ret = _console->GetAbsoluteAddress(returnPc);
|
||||
_callstackManager->Push(src, _prevProgramCounter, destAddr, destPc, ret, returnPc, StackFrameFlags::None);
|
||||
} else if(WsDisUtils::IsReturnInstruction(_prevOpCode) && destPc != _prevProgramCounter + GetPrevOpCodeSize()) {
|
||||
//RET used, and PC doesn't match the next instruction, so the ret was (probably) taken
|
||||
_callstackManager->Pop(destAddr, destPc);
|
||||
}
|
||||
}
|
||||
|
||||
void WsDebugger::ProcessInterrupt(uint32_t originalPc, uint32_t currentPc, bool forNmi)
|
||||
{
|
||||
AddressInfo src = _console->GetAbsoluteAddress(_prevProgramCounter);
|
||||
AddressInfo ret = _console->GetAbsoluteAddress(originalPc);
|
||||
AddressInfo dest = _console->GetAbsoluteAddress(currentPc);
|
||||
|
||||
if(dest.Type == MemoryType::WsPrgRom && dest.Address >= 0) {
|
||||
_codeDataLogger->SetCode(dest.Address, CdlFlags::SubEntryPoint);
|
||||
}
|
||||
|
||||
//If a call/return occurred just before IRQ, it needs to be processed now
|
||||
ProcessCallStackUpdates(ret, originalPc);
|
||||
ResetPrevOpCode();
|
||||
|
||||
_debugger->InternalProcessInterrupt(
|
||||
CpuType::Ws, *this, *_step.get(),
|
||||
src, _prevProgramCounter, dest, currentPc, ret, originalPc, forNmi
|
||||
);
|
||||
}
|
||||
|
||||
void WsDebugger::ProcessPpuCycle()
|
||||
{
|
||||
if(_ppuTools->HasOpenedViewer()) {
|
||||
_ppuTools->UpdateViewers(_ppu->GetScanline(), _ppu->GetCycle());
|
||||
}
|
||||
|
||||
if(_step->HasRequest) {
|
||||
if(_step->HasScanlineBreakRequest() && _ppu->GetCycle() == 0 && _ppu->GetScanline() == _step->BreakScanline) {
|
||||
_debugger->SleepUntilResume(CpuType::Ws, _step->GetBreakSource());
|
||||
} else if(_step->PpuStepCount > 0) {
|
||||
_step->PpuStepCount--;
|
||||
if(_step->PpuStepCount == 0) {
|
||||
_debugger->SleepUntilResume(CpuType::Ws, _step->GetBreakSource());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DebuggerFeatures WsDebugger::GetSupportedFeatures()
|
||||
{
|
||||
DebuggerFeatures features = {};
|
||||
features.RunToIrq = true;
|
||||
features.RunToNmi = false;
|
||||
features.StepOver = true;
|
||||
features.StepOut = true;
|
||||
features.StepBack = true;
|
||||
features.CallStack = true;
|
||||
features.CpuCycleStep = false;
|
||||
features.ChangeProgramCounter = AllowChangeProgramCounter;
|
||||
|
||||
features.CpuVectors[0] = { "DIV0", 0, VectorType::x86 };
|
||||
features.CpuVectors[1] = { "TRAP", 1 * 4, VectorType::x86 };
|
||||
features.CpuVectors[2] = { "INT3", 3 * 4, VectorType::x86 };
|
||||
features.CpuVectors[3] = { "INTO", 4 * 4, VectorType::x86 };
|
||||
features.CpuVectors[4] = { "BOUND", 5 * 4, VectorType::x86 };
|
||||
|
||||
features.IrqVectorOffset = _memoryManager->GetState().IrqVectorOffset;
|
||||
features.CpuVectors[5] = { "UART Send", 0, VectorType::x86WithOffset };
|
||||
features.CpuVectors[6] = { "Key Pressed", 1, VectorType::x86WithOffset };
|
||||
features.CpuVectors[7] = { "Cartridge", 2, VectorType::x86WithOffset };
|
||||
features.CpuVectors[8] = { "UART Receive", 3, VectorType::x86WithOffset };
|
||||
features.CpuVectors[9] = { "Scanline", 4, VectorType::x86WithOffset };
|
||||
features.CpuVectors[10] = { "V.Blank Timer", 5, VectorType::x86WithOffset };
|
||||
features.CpuVectors[11] = { "Vertical Blank", 6, VectorType::x86WithOffset };
|
||||
features.CpuVectors[12] = { "H.Blank Timer", 7, VectorType::x86WithOffset };
|
||||
features.CpuVectorCount = 13;
|
||||
|
||||
return features;
|
||||
}
|
||||
|
||||
void WsDebugger::SetProgramCounter(uint32_t addr, bool updateDebuggerOnly)
|
||||
{
|
||||
if(!updateDebuggerOnly) {
|
||||
WsCpuState& state = _cpu->GetState();
|
||||
WsDisUtils::UpdateAddressCsIp(addr, state);
|
||||
_cpu->ClearPrefetch();
|
||||
}
|
||||
|
||||
_prevOpCode = WsDisUtils::GetFullOpCode(_cpu->GetState().CS, _cpu->GetState().IP, _memoryManager);
|
||||
_prevProgramCounter = addr;
|
||||
}
|
||||
|
||||
uint32_t WsDebugger::GetProgramCounter(bool getInstPc)
|
||||
{
|
||||
return getInstPc ? _prevProgramCounter : _cpu->GetProgramCounter();
|
||||
}
|
||||
|
||||
uint64_t WsDebugger::GetCpuCycleCount(bool forProfiler)
|
||||
{
|
||||
return _cpu->GetState().CycleCount;
|
||||
}
|
||||
|
||||
void WsDebugger::ResetPrevOpCode()
|
||||
{
|
||||
_prevOpCode = 0x90; //nop
|
||||
}
|
||||
|
||||
BaseEventManager* WsDebugger::GetEventManager()
|
||||
{
|
||||
return _eventManager.get();
|
||||
}
|
||||
|
||||
IAssembler* WsDebugger::GetAssembler()
|
||||
{
|
||||
return _assembler.get();
|
||||
}
|
||||
|
||||
CallstackManager* WsDebugger::GetCallstackManager()
|
||||
{
|
||||
return _callstackManager.get();
|
||||
}
|
||||
|
||||
BreakpointManager* WsDebugger::GetBreakpointManager()
|
||||
{
|
||||
return _breakpointManager.get();
|
||||
}
|
||||
|
||||
BaseState& WsDebugger::GetState()
|
||||
{
|
||||
return _cpu->GetState();
|
||||
}
|
||||
|
||||
void WsDebugger::GetPpuState(BaseState& state)
|
||||
{
|
||||
(WsPpuState&)state = _ppu->GetState();
|
||||
}
|
||||
|
||||
void WsDebugger::SetPpuState(BaseState& srcState)
|
||||
{
|
||||
WsPpuState& dstState = _ppu->GetState();
|
||||
dstState = (WsPpuState&)srcState;
|
||||
}
|
||||
|
||||
ITraceLogger* WsDebugger::GetTraceLogger()
|
||||
{
|
||||
return _traceLogger.get();
|
||||
}
|
||||
|
||||
PpuTools* WsDebugger::GetPpuTools()
|
||||
{
|
||||
return _ppuTools.get();
|
||||
}
|
||||
|
||||
bool WsDebugger::SaveRomToDisk(string filename, bool saveAsIps, CdlStripOption stripOption)
|
||||
{
|
||||
vector<uint8_t> output;
|
||||
|
||||
uint8_t* prgRom = _debugger->GetMemoryDumper()->GetMemoryBuffer(MemoryType::WsPrgRom);
|
||||
uint32_t prgRomSize = _debugger->GetMemoryDumper()->GetMemorySize(MemoryType::WsPrgRom);
|
||||
vector<uint8_t> rom = vector<uint8_t>(prgRom, prgRom + prgRomSize);
|
||||
|
||||
if(saveAsIps) {
|
||||
vector<uint8_t> originalRom;
|
||||
_emu->GetRomInfo().RomFile.ReadFile(originalRom);
|
||||
|
||||
output = IpsPatcher::CreatePatch(originalRom, rom);
|
||||
} else {
|
||||
if(stripOption != CdlStripOption::StripNone) {
|
||||
_codeDataLogger->StripData(rom.data(), stripOption);
|
||||
|
||||
//Preserve WS header
|
||||
memcpy(rom.data() + rom.size() - 0x10, prgRom + prgRomSize - 0x10, 0x10);
|
||||
}
|
||||
output = rom;
|
||||
}
|
||||
|
||||
ofstream file(filename, ios::out | ios::binary);
|
||||
if(file) {
|
||||
file.write((char*)output.data(), output.size());
|
||||
file.close();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void WsDebugger::ProcessInputOverrides(DebugControllerState inputOverrides[8])
|
||||
{
|
||||
BaseControlManager* controlManager = _console->GetControlManager();
|
||||
for(int i = 0; i < 8; i++) {
|
||||
shared_ptr<WsController> controller = std::dynamic_pointer_cast<WsController>(controlManager->GetControlDeviceByIndex(i));
|
||||
if(controller && inputOverrides[i].HasPressedButton()) {
|
||||
controller->SetBitValue(WsController::Buttons::A, inputOverrides[i].A);
|
||||
controller->SetBitValue(WsController::Buttons::B, inputOverrides[i].B);
|
||||
controller->SetBitValue(WsController::Buttons::Start, inputOverrides[i].Start);
|
||||
controller->SetBitValue(WsController::Buttons::Sound, inputOverrides[i].Select);
|
||||
controller->SetBitValue(WsController::Buttons::Up, inputOverrides[i].Up);
|
||||
controller->SetBitValue(WsController::Buttons::Down, inputOverrides[i].Down);
|
||||
controller->SetBitValue(WsController::Buttons::Left, inputOverrides[i].Left);
|
||||
controller->SetBitValue(WsController::Buttons::Right, inputOverrides[i].Right);
|
||||
controller->SetBitValue(WsController::Buttons::Up2, inputOverrides[i].U);
|
||||
controller->SetBitValue(WsController::Buttons::Down2, inputOverrides[i].D);
|
||||
controller->SetBitValue(WsController::Buttons::Left2, inputOverrides[i].L);
|
||||
controller->SetBitValue(WsController::Buttons::Right2, inputOverrides[i].R);
|
||||
}
|
||||
}
|
||||
controlManager->RefreshHubState();
|
||||
}
|
||||
|
||||
template void WsDebugger::ProcessMemoryAccess<MemoryOperationType::Read>(uint32_t addr, uint8_t value, MemoryType memType);
|
||||
template void WsDebugger::ProcessMemoryAccess<MemoryOperationType::Read>(uint32_t addr, uint16_t value, MemoryType memType);
|
||||
template void WsDebugger::ProcessMemoryAccess<MemoryOperationType::Write>(uint32_t addr, uint8_t value, MemoryType memType);
|
||||
template void WsDebugger::ProcessMemoryAccess<MemoryOperationType::Write>(uint32_t addr, uint16_t value, MemoryType memType);
|
||||
|
||||
template void WsDebugger::ProcessRead<1>(uint32_t addr, uint16_t value, MemoryOperationType type);
|
||||
template void WsDebugger::ProcessRead<2>(uint32_t addr, uint16_t value, MemoryOperationType type);
|
||||
|
||||
template void WsDebugger::ProcessWrite<1>(uint32_t addr, uint16_t value, MemoryOperationType type);
|
||||
template void WsDebugger::ProcessWrite<2>(uint32_t addr, uint16_t value, MemoryOperationType type);
|
||||
@@ -0,0 +1,102 @@
|
||||
#pragma once
|
||||
#include "pch.h"
|
||||
#include "Debugger/DebugTypes.h"
|
||||
#include "Debugger/IDebugger.h"
|
||||
|
||||
class MemoryDumper;
|
||||
class Disassembler;
|
||||
class Debugger;
|
||||
class WsTraceLogger;
|
||||
class WsConsole;
|
||||
class WsMemoryManager;
|
||||
class CallstackManager;
|
||||
class MemoryAccessCounter;
|
||||
class BreakpointManager;
|
||||
class EmuSettings;
|
||||
class WsAssembler;
|
||||
class Emulator;
|
||||
class CodeDataLogger;
|
||||
class DummyWsCpu;
|
||||
class WsCpu;
|
||||
class WsPpu;
|
||||
class WsPpuTools;
|
||||
class WsEventManager;
|
||||
class DummyWsCpu;
|
||||
|
||||
enum class MemoryOperationType;
|
||||
|
||||
class WsDebugger final : public IDebugger
|
||||
{
|
||||
Debugger* _debugger = nullptr;
|
||||
MemoryDumper* _memoryDumper = nullptr;
|
||||
Emulator* _emu = nullptr;
|
||||
WsCpu* _cpu = nullptr;
|
||||
WsPpu* _ppu = nullptr;
|
||||
WsMemoryManager* _memoryManager = nullptr;
|
||||
Disassembler* _disassembler = nullptr;
|
||||
MemoryAccessCounter* _memoryAccessCounter = nullptr;
|
||||
WsConsole* _console = nullptr;
|
||||
EmuSettings* _settings = nullptr;
|
||||
|
||||
unique_ptr<WsEventManager> _eventManager;
|
||||
unique_ptr<WsPpuTools> _ppuTools;
|
||||
unique_ptr<CallstackManager> _callstackManager;
|
||||
unique_ptr<CodeDataLogger> _codeDataLogger;
|
||||
unique_ptr<BreakpointManager> _breakpointManager;
|
||||
unique_ptr<WsAssembler> _assembler;
|
||||
unique_ptr<WsTraceLogger> _traceLogger;
|
||||
unique_ptr<DummyWsCpu> _dummyCpu;
|
||||
|
||||
uint16_t _prevOpCode = 0;
|
||||
uint32_t _prevProgramCounter = 0;
|
||||
|
||||
string _cdlFile;
|
||||
|
||||
__forceinline uint8_t GetPrevOpCodeSize();
|
||||
__forceinline void ProcessCallStackUpdates(AddressInfo& destAddr, uint32_t destPc);
|
||||
|
||||
public:
|
||||
WsDebugger(Debugger* debugger);
|
||||
~WsDebugger();
|
||||
|
||||
void Reset() override;
|
||||
|
||||
void ProcessInstruction();
|
||||
|
||||
template<uint8_t accessWidth> void ProcessRead(uint32_t addr, uint16_t value, MemoryOperationType type);
|
||||
template<uint8_t accessWidth> void ProcessWrite(uint32_t addr, uint16_t value, MemoryOperationType type);
|
||||
|
||||
template<MemoryOperationType opType, typename T>
|
||||
void ProcessMemoryAccess(uint32_t addr, T value, MemoryType memType);
|
||||
|
||||
void ProcessInterrupt(uint32_t originalPc, uint32_t currentPc, bool forNmi) override;
|
||||
void ProcessPpuCycle();
|
||||
|
||||
void Run() override;
|
||||
void Step(int32_t stepCount, StepType type) override;
|
||||
StepBackConfig GetStepBackConfig() override;
|
||||
|
||||
void DrawPartialFrame() override;
|
||||
|
||||
bool SaveRomToDisk(string filename, bool saveAsIps, CdlStripOption stripOption);
|
||||
|
||||
void ProcessInputOverrides(DebugControllerState inputOverrides[8]) override;
|
||||
|
||||
void SetProgramCounter(uint32_t addr, bool updateDebuggerOnly = false) override;
|
||||
uint32_t GetProgramCounter(bool getInstPc) override;
|
||||
uint64_t GetCpuCycleCount(bool forProfiler) override;
|
||||
void ResetPrevOpCode() override;
|
||||
|
||||
DebuggerFeatures GetSupportedFeatures() override;
|
||||
|
||||
BaseEventManager* GetEventManager() override;
|
||||
IAssembler* GetAssembler() override;
|
||||
CallstackManager* GetCallstackManager() override;
|
||||
BreakpointManager* GetBreakpointManager() override;
|
||||
ITraceLogger* GetTraceLogger() override;
|
||||
PpuTools* GetPpuTools() override;
|
||||
|
||||
BaseState& GetState() override;
|
||||
void GetPpuState(BaseState& state) override;
|
||||
void SetPpuState(BaseState& state) override;
|
||||
};
|
||||
@@ -0,0 +1,729 @@
|
||||
#include "pch.h"
|
||||
#include "WS/Debugger/WsDisUtils.h"
|
||||
#include "WS/Debugger/DummyWsCpu.h"
|
||||
#include "WS/WsTypes.h"
|
||||
#include "WS/WsMemoryManager.h"
|
||||
#include "WS/WsConsole.h"
|
||||
#include "Debugger/DisassemblyInfo.h"
|
||||
#include "Debugger/LabelManager.h"
|
||||
#include "Debugger/MemoryDumper.h"
|
||||
#include "Shared/MemoryType.h"
|
||||
#include "Shared/EmuSettings.h"
|
||||
#include "Utilities/FastString.h"
|
||||
#include "Utilities/HexUtilities.h"
|
||||
|
||||
static constexpr const char* _modRegLut8[8] = { "AL", "CL", "DL", "BL", "AH", "CH", "DH", "BH" };
|
||||
static constexpr const char* _modRegLut16[8] = { "AX", "CX", "DX", "BX", "SP", "BP", "SI", "DI" };
|
||||
static constexpr const char* _modSegRegLut[4] = { "ES", "CS", "SS", "DS" };
|
||||
|
||||
constexpr const char* _opTemplate[256] = {
|
||||
//0-0F
|
||||
"ADD m", "ADD m", "ADD m", "ADD m", "ADD AL, i", "ADD AX, j", "PUSH ES", "POP ES",
|
||||
"OR m", "OR m", "OR m", "OR m", "OR AL, i", "OR AX, j", "PUSH CS", "UNDEFINED_0F",
|
||||
|
||||
//10-1F
|
||||
"ADC m", "ADC m", "ADC m", "ADC m", "ADC AL, i", "ADC AX, j", "PUSH SS", "POP SS",
|
||||
"SBB m", "SBB m", "SBB m", "SBB m", "SBB AL, i", "SBB AX, j", "PUSH DS", "POP DS",
|
||||
|
||||
//20-2F
|
||||
"AND m", "AND m", "AND m", "AND m", "AND AL, i", "AND AX, j", "ES:", "DAA",
|
||||
"SUB m", "SUB m", "SUB m", "SUB m", "SUB AL, i", "SUB AX, j", "CS:", "DAS",
|
||||
|
||||
//30-3F
|
||||
"XOR m", "XOR m", "XOR m", "XOR m", "XOR AL, i", "XOR AX, j", "SS:", "AAA",
|
||||
"CMP m", "CMP m", "CMP m", "CMP m", "CMP AL, i", "CMP AX, j", "DS:", "AAS",
|
||||
|
||||
//40-4F
|
||||
"INC AX", "INC CX", "INC DX", "INC BX", "INC SP", "INC BP", "INC SI", "INC DI",
|
||||
"DEC AX", "DEC CX", "DEC DX", "DEC BX", "DEC SP", "DEC BP", "DEC SI", "DEC DI",
|
||||
|
||||
//50-5F
|
||||
"PUSH AX", "PUSH CX", "PUSH DX", "PUSH BX", "PUSH SP", "PUSH BP", "PUSH SI", "PUSH DI",
|
||||
"POP AX", "POP CX", "POP DX", "POP BX", "POP SP", "POP BP", "POP SI", "POP DI",
|
||||
|
||||
//60-6F
|
||||
"PUSHA", "POPA", "BOUND p", "UNDEFINED_63", "UNDEFINED_64", "UNDEFINED_65", "UNDEFINED_66", "UNDEFINED_67",
|
||||
"PUSH j", "IMUL n, j", "PUSH i", "IMUL n, i", "INSB", "INSW", "OUTSB", "OUTSW",
|
||||
|
||||
//70-7F
|
||||
"JO r", "JNO r", "JB r", "JNB r", "JZ r", "JNZ r", "JBE r", "JA r",
|
||||
"JS r", "JNS r", "JPE r", "JPO r", "JL r", "JGE r", "JLE r", "JG r",
|
||||
|
||||
//80-8F
|
||||
"v", "v", "v", "v", "TEST m", "TEST m", "XCHG m", "XCHG m",
|
||||
"MOV m", "MOV m", "MOV m", "MOV m", "MOV q", "LEA o", "MOV q", "POP n",
|
||||
|
||||
//90-9F
|
||||
"NOP", "XCHG CX, AX", "XCHG DX, AX", "XCHG BX, AX", "XCHG SP, AX", "XCHG BP, AX", "XCHG SI, AX", "XCHG DI, AX",
|
||||
"CBW", "CWD", "CALL t", "WAIT", "PUSHF", "POPF", "SAHF", "LAHF",
|
||||
|
||||
//A0-AF
|
||||
"MOV AL, a", "MOV AX, a", "MOV a, AL", "MOV a, AX", "MOVSB", "MOVSW", "CMPSB", "CMPSW",
|
||||
"TEST AL, i", "TEST AX, j", "STOSB", "STOSW", "LODSB", "LODSW", "SCASB", "SCASW",
|
||||
|
||||
//B0-BF
|
||||
"MOV AL, i", "MOV CL, i", "MOV DL, i", "MOV BL, i", "MOV AH, i", "MOV CH, i", "MOV DH, i", "MOV BH, i",
|
||||
"MOV AX, j", "MOV CX, j", "MOV DX, j", "MOV BX, j", "MOV SP, j", "MOV BP, j", "MOV SI, j", "MOV DI, j",
|
||||
|
||||
//C0-CF
|
||||
"w, i", "w, i", "RET j", "RET", "LES o", "LDS o", "MOV n, i", "MOV n, j",
|
||||
"ENTER j, i", "LEAVE", "RETF j", "RETF", "INT 3", "INT i" , "INTO", "IRET",
|
||||
|
||||
//D0-DF
|
||||
"w", "w", "w, CL", "w, CL", "AAM i", "AAD i", "SALC", "XLAT",
|
||||
"UNDEFINED_D8", "UNDEFINED_D9", "UNDEFINED_DA", "UNDEFINED_DB", "UNDEFINED_DC", "UNDEFINED_DD", "UNDEFINED_DE", "UNDEFINED_DF",
|
||||
|
||||
//E0-EF
|
||||
"LOOPNZ r", "LOOPZ r", "LOOP r", "JCXZ r", "IN AL, i", "IN AX, i", "OUT i, AL", "OUT i, AX",
|
||||
"CALL s", "JMP s", "JMP t", "JMP r", "IN AL, DX", "IN AX, DX", "OUT DX, AL", "OUT DX, AX",
|
||||
|
||||
//F0-FF
|
||||
"LOCK", "UNDEFINED_F1", "REPNZ", "REPZ", "HLT", "CMC", "x", "x",
|
||||
"CLC", "STC", "CLI", "STI", "CLD", "STD", "y", "z"
|
||||
};
|
||||
|
||||
void WsDisUtils::GetDisassembly(DisassemblyInfo& info, string& out, uint32_t memoryAddr, LabelManager* labelManager, EmuSettings* settings)
|
||||
{
|
||||
FastString str(settings->GetDebugConfig().UseLowerCaseDisassembly);
|
||||
|
||||
uint8_t* byteCode = info.GetByteCode();
|
||||
uint8_t opCode = byteCode[0];
|
||||
|
||||
WsSegment segment = WsSegment::Default;
|
||||
while(IsPrefix(opCode)) {
|
||||
switch(opCode) {
|
||||
case 0x26: segment = WsSegment::ES; break;
|
||||
case 0x36: segment = WsSegment::SS; break;
|
||||
case 0x2E: segment = WsSegment::CS; break;
|
||||
case 0x3E: segment = WsSegment::DS; break;
|
||||
|
||||
case 0xF0:
|
||||
case 0xF2:
|
||||
case 0xF3:
|
||||
//rep, lock
|
||||
str.WriteAll(_opTemplate[opCode], ' ');
|
||||
break;
|
||||
}
|
||||
|
||||
byteCode++;
|
||||
opCode = byteCode[0];
|
||||
}
|
||||
|
||||
const char* op = _opTemplate[opCode];
|
||||
|
||||
int i = 0;
|
||||
while(op[i]) {
|
||||
switch(op[i]) {
|
||||
//Address offset
|
||||
case 'a':
|
||||
GetSegment(str, segment, "DS");
|
||||
str.WriteAll("[$", HexUtilities::ToHex((uint16_t)(byteCode[1] | (byteCode[2] << 8))), "]");
|
||||
break;
|
||||
|
||||
//Immediate values
|
||||
case 'i': str.WriteAll("$", HexUtilities::ToHex(byteCode[1])); byteCode++; break;
|
||||
case 'j': str.WriteAll("$", HexUtilities::ToHex((uint16_t)(byteCode[1] | (byteCode[2] << 8)))); byteCode += 2; break;
|
||||
|
||||
//ModRM (2 operands)
|
||||
case 'm': {
|
||||
bool word = opCode & 0x01;
|
||||
bool direction = opCode & 0x02;
|
||||
|
||||
if(direction) {
|
||||
GetModRegParam(str, byteCode, word);
|
||||
str.Write(", ");
|
||||
GetModRmParam(str, byteCode, segment, word);
|
||||
} else {
|
||||
GetModRmParam(str, byteCode, segment, word);
|
||||
str.Write(", ");
|
||||
GetModRegParam(str, byteCode, word);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
//ModRM (+ immediate value)
|
||||
case 'n': {
|
||||
bool word = op[i] == 'l';
|
||||
byteCode += GetModRmParam(str, byteCode, segment, word);
|
||||
break;
|
||||
}
|
||||
|
||||
//ModRM (2 operands, LEA/LDS/LES)
|
||||
case 'o':
|
||||
GetModRegParam(str, byteCode, true);
|
||||
str.Write(", ");
|
||||
GetModRmParam(str, byteCode, segment, true, true);
|
||||
break;
|
||||
|
||||
//ModRM (2 operands, BOUND)
|
||||
case 'p':
|
||||
GetModRegParam(str, byteCode, true);
|
||||
str.Write(", ");
|
||||
GetModRmParam(str, byteCode, segment, true, false);
|
||||
break;
|
||||
|
||||
//ModRM (mov segment)
|
||||
case 'q': {
|
||||
bool direction = opCode & 0x02;
|
||||
if(direction) {
|
||||
GetModSegRegParam(str, byteCode);
|
||||
str.Write(", ");
|
||||
GetModRmParam(str, byteCode, segment, true);
|
||||
} else {
|
||||
GetModRmParam(str, byteCode, segment, true);
|
||||
str.Write(", ");
|
||||
GetModSegRegParam(str, byteCode);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
//Relative jumps
|
||||
case 'r': GetJmpDestination(str, byteCode, 1); break;
|
||||
case 's': GetJmpDestination(str, byteCode, 2); break;
|
||||
case 't': GetJmpDestination(str, byteCode, 4); break;
|
||||
|
||||
case 'v': //GRP1
|
||||
switch((byteCode[1] >> 3) & 0x07) {
|
||||
case 0: str.Write("ADD "); break;
|
||||
case 1: str.Write("OR "); break;
|
||||
case 2: str.Write("ADC "); break;
|
||||
case 3: str.Write("SBB "); break;
|
||||
case 4: str.Write("AND "); break;
|
||||
case 5: str.Write("SUB "); break;
|
||||
case 6: str.Write("XOR "); break;
|
||||
case 7: str.Write("CMP "); break;
|
||||
}
|
||||
|
||||
byteCode += GetModRmParam(str, byteCode, segment, opCode & 0x01);
|
||||
|
||||
str.Write(", ");
|
||||
if((opCode & 0x03) == 0x01) {
|
||||
str.WriteAll("$", HexUtilities::ToHex((uint16_t)(byteCode[1] | (byteCode[2] << 8))));
|
||||
} else if((opCode & 0x03) == 0x03) {
|
||||
str.WriteAll("$", HexUtilities::ToHex((uint16_t)(int16_t)(int8_t)byteCode[1]));
|
||||
} else {
|
||||
str.WriteAll("$", HexUtilities::ToHex(byteCode[1]));
|
||||
}
|
||||
break;
|
||||
|
||||
case 'w': { //GRP2
|
||||
switch((byteCode[1] >> 3) & 0x07) {
|
||||
case 0: str.Write("ROL "); break;
|
||||
case 1: str.Write("ROR "); break;
|
||||
case 2: str.Write("RCL "); break;
|
||||
case 3: str.Write("RCR "); break;
|
||||
case 4: str.Write("SHL "); break;
|
||||
case 5: str.Write("SHR "); break;
|
||||
case 6: str.Write("UNDEFINED_GRP2_6 "); break;
|
||||
case 7: str.Write("SAR "); break;
|
||||
}
|
||||
|
||||
//Followed by an immediate value for C0/C1
|
||||
byteCode += GetModRmParam(str, byteCode, segment, opCode & 0x01);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'x': { //GRP3
|
||||
uint8_t mode = (byteCode[1] >> 3) & 0x07;
|
||||
bool word = opCode & 0x01;
|
||||
switch(mode) {
|
||||
case 0: str.Write("TEST "); break;
|
||||
case 1: str.Write("UNDEFINED_GRP3_1 "); break;
|
||||
case 2: str.Write("NOT "); break;
|
||||
case 3: str.Write("NEG "); break;
|
||||
case 4: str.WriteAll("MUL ", word ? "AX, " : ""); break;
|
||||
case 5: str.WriteAll("IMUL ", word ? "AX, " : ""); break;
|
||||
case 6: str.WriteAll("DIV ", word ? "AX, " : ""); break;
|
||||
case 7: str.WriteAll("IDIV ", word ? "AX, " : ""); break;
|
||||
}
|
||||
byteCode += GetModRmParam(str, byteCode, segment, word);
|
||||
if(mode == 0) {
|
||||
if(word) {
|
||||
str.WriteAll(", $", HexUtilities::ToHex((uint16_t)(byteCode[1] | (byteCode[2] << 8))));
|
||||
} else {
|
||||
str.WriteAll(", $", HexUtilities::ToHex(byteCode[1]));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'y': //grp 4
|
||||
case 'z': //grp 5
|
||||
switch((byteCode[1] >> 3) & 0x07) {
|
||||
case 0: str.Write("INC "); break;
|
||||
case 1: str.Write("DEC "); break;
|
||||
case 2: str.Write("CALL "); break;
|
||||
case 3: str.Write("CALL FAR "); break;
|
||||
case 4: str.Write("JMP "); break;
|
||||
case 5: str.Write("JMP FAR "); break;
|
||||
case 6: str.Write("PUSH "); break;
|
||||
case 7: str.Write("UNDEFINED_GRP5_7 "); break;
|
||||
}
|
||||
GetModRmParam(str, byteCode, segment, opCode & 0x01);
|
||||
break;
|
||||
|
||||
default: str.Write(op[i]); break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
out += str.ToString();
|
||||
}
|
||||
|
||||
void WsDisUtils::GetJmpDestination(FastString& str, uint8_t* byteCode, uint8_t size)
|
||||
{
|
||||
if(size == 4) {
|
||||
str.WriteAll("$", HexUtilities::ToHex((uint16_t)(byteCode[3] | (byteCode[4] << 8))));
|
||||
str.WriteAll(":$", HexUtilities::ToHex((uint16_t)(byteCode[1] | (byteCode[2] << 8))));
|
||||
} else if(size == 2) {
|
||||
uint16_t jmpOffset = (uint16_t)(byteCode[1] | (byteCode[2] << 8));
|
||||
str.WriteAll("+$", HexUtilities::ToHex(jmpOffset));
|
||||
} else if(size == 1) {
|
||||
uint8_t jmpOffset = (uint8_t)byteCode[1];
|
||||
str.WriteAll("+$", HexUtilities::ToHex(jmpOffset));
|
||||
}
|
||||
}
|
||||
|
||||
void WsDisUtils::GetSegment(FastString& str, WsSegment segment, const char* defaultSegment)
|
||||
{
|
||||
switch(segment) {
|
||||
default: case WsSegment::Default: str.WriteAll(defaultSegment, ':'); break;
|
||||
case WsSegment::ES: str.Write("ES:"); break;
|
||||
case WsSegment::SS: str.Write("SS:"); break;
|
||||
case WsSegment::CS: str.Write("CS:"); break;
|
||||
case WsSegment::DS: str.Write("DS:"); break;
|
||||
}
|
||||
}
|
||||
|
||||
void WsDisUtils::GetModRegParam(FastString& str, uint8_t* byteCode, bool word)
|
||||
{
|
||||
uint8_t modRm = byteCode[1];
|
||||
uint8_t reg = (modRm >> 3) & 0x07;
|
||||
str.Write(word ? _modRegLut16[reg] : _modRegLut8[reg]);
|
||||
}
|
||||
|
||||
void WsDisUtils::GetModSegRegParam(FastString& str, uint8_t* byteCode)
|
||||
{
|
||||
uint8_t modRm = byteCode[1];
|
||||
uint8_t reg = (modRm >> 3) & 0x03;
|
||||
str.Write(_modSegRegLut[reg]);
|
||||
}
|
||||
|
||||
int WsDisUtils::GetModRmParam(FastString& str, uint8_t* byteCode, WsSegment segment, bool word, bool forLeaLdsLes)
|
||||
{
|
||||
uint8_t modRm = byteCode[1];
|
||||
uint8_t mode = (modRm >> 6) & 0x03;
|
||||
uint8_t rm = modRm & 0x07;
|
||||
int len = 1;
|
||||
|
||||
string param;
|
||||
string param2;
|
||||
|
||||
if(mode == 3) {
|
||||
if(forLeaLdsLes) {
|
||||
switch(rm) {
|
||||
case 0x00: GetSegment(str, segment, "DS"); str.Write("[BX+AX]"); break;
|
||||
case 0x01: GetSegment(str, segment, "DS"); str.Write("[BX+CX]"); break;
|
||||
case 0x02: GetSegment(str, segment, "SS"); str.Write("[BP+DX]"); break;
|
||||
case 0x03: GetSegment(str, segment, "SS"); str.Write("[BP+BX]"); break;
|
||||
case 0x04: GetSegment(str, segment, "DS"); str.Write("[SP+SI]"); break;
|
||||
case 0x05: GetSegment(str, segment, "DS"); str.Write("[BP+DI]"); break;
|
||||
case 0x06: GetSegment(str, segment, "SS"); str.Write("[BP+SI]"); break;
|
||||
case 0x07: GetSegment(str, segment, "DS"); str.Write("[BX+DI]"); break;
|
||||
}
|
||||
} else {
|
||||
str.Write(word ? _modRegLut16[rm] : _modRegLut8[rm]);
|
||||
}
|
||||
} else if(mode == 0 && rm == 0x06) {
|
||||
GetSegment(str, segment, "DS");
|
||||
str.WriteAll("[$", HexUtilities::ToHex((uint16_t)(byteCode[2] | (byteCode[3] << 8))), ']');
|
||||
len += 2;
|
||||
} else {
|
||||
switch(rm) {
|
||||
case 0x00: GetSegment(str, segment, "DS"); str.Write("[BX+SI"); break;
|
||||
case 0x01: GetSegment(str, segment, "DS"); str.Write("[BX+DI"); break;
|
||||
case 0x02: GetSegment(str, segment, "SS"); str.Write("[BP+SI"); break;
|
||||
case 0x03: GetSegment(str, segment, "SS"); str.Write("[BP+DI"); break;
|
||||
case 0x04: GetSegment(str, segment, "DS"); str.Write("[SI"); break;
|
||||
case 0x05: GetSegment(str, segment, "DS"); str.Write("[DI"); break;
|
||||
case 0x06: GetSegment(str, segment, "SS"); str.Write("[BP"); break;
|
||||
case 0x07: GetSegment(str, segment, "DS"); str.Write("[BX"); break;
|
||||
}
|
||||
|
||||
if(mode == 1) {
|
||||
str.WriteAll("+$", HexUtilities::ToHex((uint8_t)byteCode[2]), ']');
|
||||
len++;
|
||||
} else if(mode == 2) {
|
||||
str.WriteAll("+$", HexUtilities::ToHex((uint16_t)(byteCode[2] | (byteCode[3] << 8))), ']');
|
||||
len += 2;
|
||||
} else {
|
||||
str.Write(']');
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
EffectiveAddressInfo WsDisUtils::GetEffectiveAddress(DisassemblyInfo& info, WsConsole* console, WsCpuState& state)
|
||||
{
|
||||
DummyWsCpu dummyCpu(nullptr, console->GetMemoryManager());
|
||||
dummyCpu.SetDummyState(state);
|
||||
dummyCpu.Exec();
|
||||
|
||||
uint16_t opCode = WsDisUtils::GetFullOpCode(info);
|
||||
if(WsDisUtils::IsReturnInstruction(opCode) || WsDisUtils::IsPushPopInstruction(opCode)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if((uint8_t)opCode == 0xC8 || (uint8_t)opCode == 0xC9) {
|
||||
//Show nothing for Enter/Leave
|
||||
return {};
|
||||
}
|
||||
|
||||
if(WsDisUtils::IsConditionalJump(opCode) || WsDisUtils::IsUnconditionalJump(opCode)) {
|
||||
EffectiveAddressInfo result;
|
||||
result.ShowAddress = true;
|
||||
result.Address = dummyCpu.GetProgramCounter();
|
||||
result.Type = MemoryType::WsMemory;
|
||||
return result;
|
||||
}
|
||||
|
||||
//For everything else, return the last read/write address
|
||||
uint32_t count = dummyCpu.GetOperationCount();
|
||||
for(int i = count - 1; i > 0; i--) {
|
||||
MemoryOperationInfo opInfo = dummyCpu.GetOperationInfo(i);
|
||||
|
||||
if(opInfo.Type != MemoryOperationType::ExecOperand) {
|
||||
MemoryOperationInfo prevOpInfo = dummyCpu.GetOperationInfo(i - 1);
|
||||
EffectiveAddressInfo result;
|
||||
result.ShowAddress = true;
|
||||
|
||||
if(dummyCpu.IsWordAccess(i)) {
|
||||
result.Address = opInfo.Address;
|
||||
result.Type = opInfo.MemType;
|
||||
result.ValueSize = 2;
|
||||
} else {
|
||||
if(prevOpInfo.Type == opInfo.Type && prevOpInfo.Address == opInfo.Address - 1) {
|
||||
//For 16-bit read/writes that were split into 2 8-bit accesses, return the first address
|
||||
result.Address = prevOpInfo.Address;
|
||||
result.Type = prevOpInfo.MemType;
|
||||
result.ValueSize = 2;
|
||||
} else {
|
||||
result.Address = opInfo.Address;
|
||||
result.Type = opInfo.MemType;
|
||||
result.ValueSize = 1;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
int WsDisUtils::GetModRmSize(uint8_t modRm)
|
||||
{
|
||||
uint8_t mode = (modRm >> 6) & 0x03;
|
||||
uint8_t rm = modRm & 0x07;
|
||||
if(mode == 2 || (mode == 0 && rm == 0x06)) {
|
||||
return 3;
|
||||
} else if(mode == 1) {
|
||||
return 2;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool WsDisUtils::IsPrefix(uint8_t opCode)
|
||||
{
|
||||
return opCode == 0xF0 || opCode == 0xF2 || opCode == 0xF3 || opCode == 0x26 || opCode == 0x36 || opCode == 0x2E || opCode == 0x3F;
|
||||
}
|
||||
|
||||
uint8_t WsDisUtils::GetOpSize(uint32_t cpuAddress, MemoryType memType, MemoryDumper* memoryDumper)
|
||||
{
|
||||
uint8_t size = 1;
|
||||
uint8_t opCode = memoryDumper->GetMemoryValue(memType, cpuAddress);
|
||||
while(IsPrefix(opCode)) {
|
||||
cpuAddress++;
|
||||
opCode = memoryDumper->GetMemoryValue(memType, cpuAddress);
|
||||
size++;
|
||||
}
|
||||
|
||||
const char* op = _opTemplate[opCode];
|
||||
|
||||
int i = 0;
|
||||
|
||||
while(op[i]) {
|
||||
switch(op[i]) {
|
||||
//Absolute address
|
||||
case 'a': size+=2; break;
|
||||
|
||||
//Immediate values
|
||||
case 'i': size++; break;
|
||||
case 'j': size += 2; break;
|
||||
|
||||
//ModRM
|
||||
case 'm':
|
||||
case 'n':
|
||||
case 'o':
|
||||
case 'p':
|
||||
case 'q':
|
||||
size += GetModRmSize(memoryDumper->GetMemoryValue(memType, cpuAddress + 1));
|
||||
break;
|
||||
|
||||
//Relative jumps
|
||||
case 'r': size++; break;
|
||||
case 's': size += 2; break;
|
||||
case 't': size += 4; break;
|
||||
|
||||
case 'v': //grp1
|
||||
size += GetModRmSize(memoryDumper->GetMemoryValue(memType, cpuAddress + 1));
|
||||
size++;
|
||||
if((opCode & 0x3) == 0x01) {
|
||||
size++;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'x': { //grp3
|
||||
uint8_t modRm = memoryDumper->GetMemoryValue(memType, cpuAddress + 1);
|
||||
size += GetModRmSize(modRm);
|
||||
if(((modRm >> 3) & 0x07) == 0) {
|
||||
size += (opCode & 0x01) ? 2 : 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'w': //grp2
|
||||
case 'y': //grp4
|
||||
case 'z': //grp5
|
||||
size += GetModRmSize(memoryDumper->GetMemoryValue(memType, cpuAddress + 1));
|
||||
break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
uint16_t WsDisUtils::GetFullOpCode(uint16_t cs, uint16_t ip, WsMemoryManager* memoryManager)
|
||||
{
|
||||
int i = 0;
|
||||
while(IsPrefix(memoryManager->DebugRead((cs << 4) + ip)) && i < 6) {
|
||||
ip++;
|
||||
i++;
|
||||
}
|
||||
|
||||
return (
|
||||
memoryManager->DebugRead((cs << 4) + ip) |
|
||||
(memoryManager->DebugRead((cs << 4) + (uint16_t)(ip + 1)) << 8)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
uint16_t WsDisUtils::GetFullOpCode(DisassemblyInfo& disInfo)
|
||||
{
|
||||
uint8_t* byteCode = disInfo.GetByteCode();
|
||||
|
||||
int i = 0;
|
||||
while(IsPrefix(byteCode[i]) && i < 6) {
|
||||
i++;
|
||||
}
|
||||
|
||||
return byteCode[i] | (byteCode[i+1] << 8);
|
||||
}
|
||||
|
||||
bool WsDisUtils::IsJumpToSub(uint16_t opCode)
|
||||
{
|
||||
switch((uint8_t)opCode) {
|
||||
case 0x9A:
|
||||
case 0xE8:
|
||||
return true;
|
||||
|
||||
case 0xFE:
|
||||
case 0xFF: {
|
||||
uint8_t mode = (opCode >> 11) & 0x07;
|
||||
switch(mode) {
|
||||
case 2: //CALL
|
||||
case 3: //CALL FAR
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool WsDisUtils::IsReturnInstruction(uint16_t opCode)
|
||||
{
|
||||
switch((uint8_t)opCode) {
|
||||
case 0xC2: //RET
|
||||
case 0xC3: //RET
|
||||
case 0xCA: //RETF
|
||||
case 0xCB: //RETF
|
||||
case 0xCF: //IRET
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool WsDisUtils::IsUnconditionalJump(uint16_t opCode)
|
||||
{
|
||||
switch((uint8_t)opCode) {
|
||||
case 0x9A: //CALL
|
||||
case 0xC2: //RET
|
||||
case 0xC3: //RET
|
||||
case 0xCA: //RETF
|
||||
case 0xCB: //RETF
|
||||
case 0xCF: //IRET
|
||||
case 0xE8: //CALL relative
|
||||
case 0xE9: //JMP
|
||||
case 0xEA: //JMP
|
||||
case 0xEB: //JMP
|
||||
return true;
|
||||
|
||||
case 0xFE:
|
||||
case 0xFF: {
|
||||
uint8_t mode = (opCode >> 11) & 0x07;
|
||||
switch(mode) {
|
||||
case 2: //CALL
|
||||
case 3: //CALL FAR
|
||||
case 4: //JMP
|
||||
case 5: //JMP FAR
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool WsDisUtils::IsConditionalJump(uint16_t opCode)
|
||||
{
|
||||
switch((uint8_t)opCode) {
|
||||
case 0x70: //JO
|
||||
case 0x71: //JNO
|
||||
case 0x72: //JB
|
||||
case 0x73: //JNB
|
||||
case 0x74: //JZ
|
||||
case 0x75: //JNZ
|
||||
case 0x76: //JBE
|
||||
case 0x77: //JA
|
||||
case 0x78: //JS
|
||||
case 0x79: //JNS
|
||||
case 0x7A: //JPE
|
||||
case 0x7B: //JPO
|
||||
case 0x7C: //JL
|
||||
case 0x7D: //JGE
|
||||
case 0x7E: //JLE
|
||||
case 0x7F: //JG
|
||||
case 0xE0: //LOOPNZ
|
||||
case 0xE1: //LOOPZ
|
||||
case 0xE2: //LOOP
|
||||
case 0xE3: //JCXZ
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool WsDisUtils::IsUndefinedOpCode(uint16_t opCode)
|
||||
{
|
||||
switch((uint8_t)opCode) {
|
||||
case 0x0F:
|
||||
case 0x63:
|
||||
case 0x64:
|
||||
case 0x65:
|
||||
case 0x66:
|
||||
case 0x67:
|
||||
case 0xD8:
|
||||
case 0xD9:
|
||||
case 0xDA:
|
||||
case 0xDB:
|
||||
case 0xDC:
|
||||
case 0xDD:
|
||||
case 0xDE:
|
||||
case 0xDF:
|
||||
case 0xF1:
|
||||
return true;
|
||||
|
||||
case 0xC0:
|
||||
case 0xC1:
|
||||
case 0xD0:
|
||||
case 0xD1:
|
||||
case 0xD2:
|
||||
case 0xD3:
|
||||
return ((opCode >> 11) & 0x07) == 0x06;
|
||||
|
||||
case 0xF6:
|
||||
case 0xF7:
|
||||
return ((opCode >> 11) & 0x07) == 0x01;
|
||||
|
||||
case 0xFE:
|
||||
case 0xFF:
|
||||
return ((opCode >> 11) & 0x07) == 0x07;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool WsDisUtils::IsPushPopInstruction(uint16_t opCode)
|
||||
{
|
||||
switch((uint8_t)opCode) {
|
||||
//Push/pop segments
|
||||
case 0x06: case 0x07: case 0x0E: case 0x16:
|
||||
case 0x17: case 0x1E: case 0x1F:
|
||||
|
||||
//Push
|
||||
case 0x50: case 0x51: case 0x52: case 0x53:
|
||||
case 0x54: case 0x55: case 0x56: case 0x57:
|
||||
|
||||
//Pop
|
||||
case 0x58: case 0x59: case 0x5A: case 0x5B:
|
||||
case 0x5C: case 0x5D: case 0x5E: case 0x5F:
|
||||
|
||||
//Push/pop all
|
||||
case 0x60: case 0x61:
|
||||
|
||||
//Push immediate
|
||||
case 0x68: case 0x6A:
|
||||
|
||||
//Push/pop flags
|
||||
case 0x9C: case 0x9D:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
CdlFlags::CdlFlags WsDisUtils::GetOpFlags(uint16_t opCode, uint32_t pc, uint32_t prevPc, uint8_t opSize)
|
||||
{
|
||||
if(WsDisUtils::IsJumpToSub(opCode)) {
|
||||
return CdlFlags::SubEntryPoint;
|
||||
} else if(WsDisUtils::IsUnconditionalJump(opCode) && !WsDisUtils::IsReturnInstruction(opCode)) {
|
||||
return CdlFlags::JumpTarget;
|
||||
} else if(WsDisUtils::IsConditionalJump(opCode) && (pc != prevPc + opSize)) {
|
||||
return CdlFlags::JumpTarget;
|
||||
}
|
||||
|
||||
return CdlFlags::None;
|
||||
}
|
||||
|
||||
void WsDisUtils::UpdateAddressCsIp(uint32_t addr, WsCpuState& state)
|
||||
{
|
||||
uint32_t baseAddr = (state.CS << 4);
|
||||
if(addr >= baseAddr && addr <= baseAddr + 0xFFFF) {
|
||||
//address fits within the current code segment, only change the instruction pointer
|
||||
state.IP = addr - baseAddr;
|
||||
} else {
|
||||
//address is outside the range of the current CS, change both values
|
||||
state.CS = (addr & 0xF0000) >> 4;
|
||||
state.IP = (addr & 0xFFFF);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
#include "pch.h"
|
||||
#include "Debugger/DebugTypes.h"
|
||||
|
||||
class WsConsole;
|
||||
class WsMemoryManager;
|
||||
class LabelManager;
|
||||
class EmuSettings;
|
||||
class MemoryDumper;
|
||||
class FastString;
|
||||
struct WsCpuState;
|
||||
enum class WsSegment : uint8_t;
|
||||
|
||||
class WsDisUtils
|
||||
{
|
||||
private:
|
||||
static void GetModRegParam(FastString& str, uint8_t* byteCode, bool word);
|
||||
static void GetModSegRegParam(FastString& str, uint8_t* byteCode);
|
||||
static int GetModRmParam(FastString& str, uint8_t* byteCode, WsSegment segment, bool word, bool forLeaLdsLes = false);
|
||||
static int GetModRmSize(uint8_t modRm);
|
||||
static bool IsPrefix(uint8_t opCode);
|
||||
static void GetJmpDestination(FastString& str, uint8_t* byteCode, uint8_t size);
|
||||
static void GetSegment(FastString& str, WsSegment segment, const char* defaultSegment);
|
||||
|
||||
public:
|
||||
static void GetDisassembly(DisassemblyInfo& info, string& out, uint32_t memoryAddr, LabelManager* labelManager, EmuSettings* settings);
|
||||
static EffectiveAddressInfo GetEffectiveAddress(DisassemblyInfo& info, WsConsole* console, WsCpuState& state);
|
||||
|
||||
static uint8_t GetOpSize(uint32_t cpuAddress, MemoryType memType, MemoryDumper* memoryDumper);
|
||||
|
||||
static uint16_t GetFullOpCode(uint16_t cs, uint16_t ip, WsMemoryManager* memoryManager);
|
||||
static uint16_t GetFullOpCode(DisassemblyInfo& disInfo);
|
||||
|
||||
static bool IsJumpToSub(uint16_t opCode);
|
||||
static bool IsReturnInstruction(uint16_t opCode);
|
||||
static bool IsUnconditionalJump(uint16_t opCode);
|
||||
static bool IsConditionalJump(uint16_t opCode);
|
||||
|
||||
static bool IsUndefinedOpCode(uint16_t opCode);
|
||||
static bool IsPushPopInstruction(uint16_t opCode);
|
||||
|
||||
static CdlFlags::CdlFlags GetOpFlags(uint16_t opCode, uint32_t pc, uint32_t prevPc, uint8_t opSize);
|
||||
static void UpdateAddressCsIp(uint32_t addr, WsCpuState& state);
|
||||
};
|
||||
@@ -0,0 +1,196 @@
|
||||
#include "pch.h"
|
||||
#include "WS/Debugger/WsEventManager.h"
|
||||
#include "WS/WsCpu.h"
|
||||
#include "WS/WsPpu.h"
|
||||
#include "WS/WsConsole.h"
|
||||
#include "Shared/ColorUtilities.h"
|
||||
#include "Debugger/DebugTypes.h"
|
||||
#include "Debugger/Debugger.h"
|
||||
#include "Debugger/DebugBreakHelper.h"
|
||||
#include "Debugger/BaseEventManager.h"
|
||||
|
||||
WsEventManager::WsEventManager(Debugger* debugger, WsConsole* console, WsCpu* cpu, WsPpu* ppu)
|
||||
{
|
||||
_debugger = debugger;
|
||||
_console = console;
|
||||
_cpu = cpu;
|
||||
_ppu = ppu;
|
||||
|
||||
_ppuBuffer = new uint16_t[WsConstants::MaxPixelCount];
|
||||
memset(_ppuBuffer, 0, WsConstants::MaxPixelCount * sizeof(uint16_t));
|
||||
}
|
||||
|
||||
WsEventManager::~WsEventManager()
|
||||
{
|
||||
delete[] _ppuBuffer;
|
||||
}
|
||||
|
||||
void WsEventManager::AddEvent(DebugEventType type, MemoryOperationInfo& operation, int32_t breakpointId)
|
||||
{
|
||||
DebugEventInfo evt = {};
|
||||
evt.Type = type;
|
||||
evt.Flags = (uint32_t)EventFlags::ReadWriteOp;
|
||||
evt.Operation = operation;
|
||||
evt.Scanline = _ppu->GetScanline();
|
||||
evt.Cycle = _ppu->GetCycle();
|
||||
evt.BreakpointId = breakpointId;
|
||||
evt.DmaChannel = -1;
|
||||
|
||||
evt.ProgramCounter = _debugger->GetProgramCounter(CpuType::Ws, true);
|
||||
_debugEvents.push_back(evt);
|
||||
}
|
||||
|
||||
void WsEventManager::AddEvent(DebugEventType type)
|
||||
{
|
||||
DebugEventInfo evt = {};
|
||||
evt.Type = type;
|
||||
evt.Scanline = _ppu->GetScanline();
|
||||
evt.Cycle = _ppu->GetCycle();
|
||||
evt.BreakpointId = -1;
|
||||
evt.DmaChannel = -1;
|
||||
evt.ProgramCounter = _cpu->GetProgramCounter();
|
||||
_debugEvents.push_back(evt);
|
||||
}
|
||||
|
||||
DebugEventInfo WsEventManager::GetEvent(uint16_t y, uint16_t x)
|
||||
{
|
||||
auto lock = _lock.AcquireSafe();
|
||||
|
||||
x /= 2; //convert to cycle value
|
||||
y /= 2; //convert to scanline value
|
||||
|
||||
//Search without including larger background color first
|
||||
for(DebugEventInfo& evt : _sentEvents) {
|
||||
if(evt.Cycle == x && evt.Scanline == y) {
|
||||
return evt;
|
||||
}
|
||||
}
|
||||
|
||||
//If no exact match, extend to the background color
|
||||
for(int i = (int)_sentEvents.size() - 1; i >= 0; i--){
|
||||
DebugEventInfo& evt = _sentEvents[i];
|
||||
if(std::abs((int)evt.Cycle - (int)x) <= 1 && std::abs((int)evt.Scanline - (int)y) <= 1) {
|
||||
return evt;
|
||||
}
|
||||
}
|
||||
|
||||
DebugEventInfo empty = {};
|
||||
empty.ProgramCounter = 0xFFFFFFFF;
|
||||
return empty;
|
||||
}
|
||||
|
||||
bool WsEventManager::ShowPreviousFrameEvents()
|
||||
{
|
||||
return _config.ShowPreviousFrameEvents;
|
||||
}
|
||||
|
||||
void WsEventManager::SetConfiguration(BaseEventViewerConfig& config)
|
||||
{
|
||||
_config = (WsEventViewerConfig&)config;
|
||||
}
|
||||
|
||||
EventViewerCategoryCfg WsEventManager::GetEventConfig(DebugEventInfo& evt)
|
||||
{
|
||||
switch(evt.Type) {
|
||||
default: return {};
|
||||
case DebugEventType::Breakpoint: return _config.MarkedBreakpoints;
|
||||
case DebugEventType::Irq: return _config.Irq;
|
||||
case DebugEventType::Register:
|
||||
bool isWrite = evt.Operation.Type == MemoryOperationType::Write || evt.Operation.Type == MemoryOperationType::DmaWrite;
|
||||
uint32_t port = evt.Operation.Address;
|
||||
if(evt.Operation.MemType == MemoryType::WsMemory) {
|
||||
if(evt.Operation.Address >= 0xFE00) {
|
||||
return isWrite ? _config.PpuPaletteWrite : _config.PpuPaletteRead;
|
||||
} else {
|
||||
return isWrite ? _config.PpuVramWrite : _config.PpuVramRead;
|
||||
}
|
||||
} else {
|
||||
if(port <= 0x3F) {
|
||||
if(port >= 0x08 && port <= 0x0F) {
|
||||
return isWrite ? _config.PpuWindowWrite : _config.PpuWindowRead;
|
||||
} else if(port >= 0x10 && port <= 0x13) {
|
||||
return isWrite ? _config.PpuScrollWrite : _config.PpuScrollRead;
|
||||
} else if(port == 0x02 && !isWrite) {
|
||||
return _config.PpuVCounterRead;
|
||||
} else if(port >= 0x1C && port <= 0x3F) {
|
||||
return isWrite ? _config.PpuPaletteWrite : _config.PpuPaletteRead;
|
||||
} else {
|
||||
return isWrite ? _config.PpuOtherWrite : _config.PpuOtherRead;
|
||||
}
|
||||
|
||||
} else if(port >= 0x40 && port <= 0x53) {
|
||||
return isWrite ? _config.DmaWrite : _config.DmaRead;
|
||||
} else if((port >= 0x80 && port <= 0x9F) || (port >= 0x64 && port <= 0x6B)) {
|
||||
return isWrite ? _config.AudioWrite : _config.AudioRead;
|
||||
} else if(port >= 0xA2 && port <= 0xAB) {
|
||||
return isWrite ? _config.TimerWrite : _config.TimerRead;
|
||||
} else if(port == 0xB0 || port == 0xB2 || port == 0xB4 || port == 0xB6 || port == 0xB7) {
|
||||
return isWrite ? _config.IrqWrite : _config.IrqRead;
|
||||
} else if(port == 0xB1 || port == 0xB3) {
|
||||
return isWrite ? _config.SerialWrite : _config.SerialRead;
|
||||
} else if(port == 0xB5) {
|
||||
return isWrite ? _config.InputWrite : _config.InputRead;
|
||||
} else if(port >= 0xBA && port <= 0xBF) {
|
||||
return isWrite ? _config.EepromWrite : _config.EepromRead;
|
||||
} else if(port >= 0xC0) {
|
||||
return isWrite ? _config.CartWrite : _config.CartRead;
|
||||
} else if(port == 0x60 || port == 0x62 || port == 0xA0 || (port >= 0x70 && port <= 0x77)) {
|
||||
//system control 1/2/3, tft control, etc.
|
||||
return isWrite ? _config.OtherWrite : _config.OtherRead;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
void WsEventManager::ConvertScanlineCycleToRowColumn(int32_t& x, int32_t& y)
|
||||
{
|
||||
y *= 2;
|
||||
x *= 2;
|
||||
}
|
||||
|
||||
uint32_t WsEventManager::TakeEventSnapshot(bool forAutoRefresh)
|
||||
{
|
||||
DebugBreakHelper breakHelper(_debugger);
|
||||
auto lock = _lock.AcquireSafe();
|
||||
|
||||
uint16_t cycle = _ppu->GetCycle();
|
||||
uint16_t scanline = _ppu->GetScanline();
|
||||
|
||||
if(scanline > _ppu->GetVisibleScanlineCount()) {
|
||||
memcpy(_ppuBuffer, _ppu->GetScreenBuffer(false), WsConstants::MaxPixelCount * sizeof(uint16_t));
|
||||
} else {
|
||||
uint32_t offset = _ppu->GetScreenWidth() * (std::max<int>(1, scanline) - 1);
|
||||
memcpy(_ppuBuffer, _ppu->GetScreenBuffer(false), offset * sizeof(uint16_t));
|
||||
memcpy(_ppuBuffer + offset, _ppu->GetScreenBuffer(true) + offset, (WsConstants::MaxPixelCount - offset) * sizeof(uint16_t));
|
||||
}
|
||||
|
||||
_snapshotCurrentFrame = _debugEvents;
|
||||
_snapshotPrevFrame = _prevDebugEvents;
|
||||
_snapshotScanline = scanline;
|
||||
_snapshotCycle = cycle;
|
||||
_forAutoRefresh = forAutoRefresh;
|
||||
_scanlineCount = _ppu->GetScanlineCount();
|
||||
return _scanlineCount;
|
||||
}
|
||||
|
||||
FrameInfo WsEventManager::GetDisplayBufferSize()
|
||||
{
|
||||
FrameInfo size;
|
||||
size.Width = WsEventManager::ScanlineWidth;
|
||||
size.Height = _scanlineCount * 2;
|
||||
return size;
|
||||
}
|
||||
|
||||
void WsEventManager::DrawScreen(uint32_t* buffer)
|
||||
{
|
||||
uint16_t *src = _ppuBuffer;
|
||||
uint16_t screenWidth = _ppu->GetScreenWidth();
|
||||
for(uint32_t y = 0, len = WsConstants::ScreenHeight * 2; y < len; y++) {
|
||||
for(uint32_t x = 0; x < WsConstants::ScreenWidth * 2; x++) {
|
||||
int srcOffset = (y >> 1) * screenWidth + (x >> 1);
|
||||
buffer[(y + 2)*WsEventManager::ScanlineWidth + x] = ColorUtilities::Bgr444ToArgb(src[srcOffset]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
#pragma once
|
||||
#include "pch.h"
|
||||
#include "WS/WsTypes.h"
|
||||
#include "Debugger/DebugTypes.h"
|
||||
#include "Debugger/BaseEventManager.h"
|
||||
#include "Utilities/SimpleLock.h"
|
||||
|
||||
enum class DebugEventType;
|
||||
struct DebugEventInfo;
|
||||
class WsCpu;
|
||||
class WsPpu;
|
||||
class WsConsole;
|
||||
class Debugger;
|
||||
|
||||
struct WsEventViewerConfig : public BaseEventViewerConfig
|
||||
{
|
||||
EventViewerCategoryCfg PpuPaletteRead;
|
||||
EventViewerCategoryCfg PpuPaletteWrite;
|
||||
EventViewerCategoryCfg PpuVramRead;
|
||||
EventViewerCategoryCfg PpuVramWrite;
|
||||
EventViewerCategoryCfg PpuVCounterRead;
|
||||
EventViewerCategoryCfg PpuScrollRead;
|
||||
EventViewerCategoryCfg PpuScrollWrite;
|
||||
EventViewerCategoryCfg PpuWindowRead;
|
||||
EventViewerCategoryCfg PpuWindowWrite;
|
||||
EventViewerCategoryCfg PpuOtherRead;
|
||||
EventViewerCategoryCfg PpuOtherWrite;
|
||||
EventViewerCategoryCfg AudioRead;
|
||||
EventViewerCategoryCfg AudioWrite;
|
||||
EventViewerCategoryCfg SerialRead;
|
||||
EventViewerCategoryCfg SerialWrite;
|
||||
EventViewerCategoryCfg DmaRead;
|
||||
EventViewerCategoryCfg DmaWrite;
|
||||
EventViewerCategoryCfg InputRead;
|
||||
EventViewerCategoryCfg InputWrite;
|
||||
EventViewerCategoryCfg IrqRead;
|
||||
EventViewerCategoryCfg IrqWrite;
|
||||
EventViewerCategoryCfg TimerRead;
|
||||
EventViewerCategoryCfg TimerWrite;
|
||||
EventViewerCategoryCfg EepromRead;
|
||||
EventViewerCategoryCfg EepromWrite;
|
||||
EventViewerCategoryCfg CartRead;
|
||||
EventViewerCategoryCfg CartWrite;
|
||||
EventViewerCategoryCfg OtherRead;
|
||||
EventViewerCategoryCfg OtherWrite;
|
||||
|
||||
EventViewerCategoryCfg Irq;
|
||||
EventViewerCategoryCfg MarkedBreakpoints;
|
||||
|
||||
bool ShowPreviousFrameEvents;
|
||||
};
|
||||
|
||||
class WsEventManager final : public BaseEventManager
|
||||
{
|
||||
private:
|
||||
static constexpr int ScanlineWidth = WsConstants::ClocksPerScanline*2;
|
||||
static constexpr int ScreenHeight = WsConstants::ScanlineCount;
|
||||
|
||||
WsEventViewerConfig _config;
|
||||
|
||||
WsPpu* _ppu;
|
||||
WsCpu* _cpu;
|
||||
WsConsole* _console;
|
||||
Debugger* _debugger;
|
||||
|
||||
uint32_t _scanlineCount = 159;
|
||||
uint16_t* _ppuBuffer = nullptr;
|
||||
|
||||
protected:
|
||||
bool ShowPreviousFrameEvents() override;
|
||||
void ConvertScanlineCycleToRowColumn(int32_t& x, int32_t& y) override;
|
||||
void DrawScreen(uint32_t* buffer) override;
|
||||
|
||||
public:
|
||||
WsEventManager(Debugger* debugger, WsConsole* console, WsCpu* cpu, WsPpu* ppu);
|
||||
~WsEventManager();
|
||||
|
||||
void AddEvent(DebugEventType type, MemoryOperationInfo& operation, int32_t breakpointId = -1) override;
|
||||
void AddEvent(DebugEventType type) override;
|
||||
|
||||
EventViewerCategoryCfg GetEventConfig(DebugEventInfo& evt) override;
|
||||
|
||||
uint32_t TakeEventSnapshot(bool forAutoRefresh) override;
|
||||
DebugEventInfo GetEvent(uint16_t y, uint16_t x) override;
|
||||
|
||||
FrameInfo GetDisplayBufferSize() override;
|
||||
void SetConfiguration(BaseEventViewerConfig& config) override;
|
||||
};
|
||||
@@ -0,0 +1,393 @@
|
||||
#include "pch.h"
|
||||
#include "WS/Debugger/WsPpuTools.h"
|
||||
#include "WS/WsTypes.h"
|
||||
#include "WS/WsConsole.h"
|
||||
#include "WS/WsPpu.h"
|
||||
#include "Debugger/DebugTypes.h"
|
||||
#include "Debugger/MemoryDumper.h"
|
||||
#include "Shared/SettingTypes.h"
|
||||
|
||||
WsPpuTools::WsPpuTools(Debugger* debugger, Emulator *emu, WsConsole* console) : PpuTools(debugger, emu)
|
||||
{
|
||||
_console = console;
|
||||
}
|
||||
|
||||
FrameInfo WsPpuTools::GetTilemapSize(GetTilemapOptions options, BaseState& baseState)
|
||||
{
|
||||
return { 256, 256 };
|
||||
}
|
||||
|
||||
DebugTilemapInfo WsPpuTools::GetTilemap(GetTilemapOptions options, BaseState& baseState, uint8_t* vram, uint32_t* palette, uint32_t* outBuffer)
|
||||
{
|
||||
WsPpuState& state = (WsPpuState&)baseState;
|
||||
|
||||
uint16_t ramMask = state.Mode == WsVideoMode::Monochrome ? 0x3FFF : 0xFFFF;
|
||||
uint16_t baseAddr = state.BgLayers[options.Layer].MapAddress;
|
||||
|
||||
std::fill(outBuffer, outBuffer + 256 * 256, 0xFFFFFFFF);
|
||||
|
||||
int tileSize = state.Mode >= WsVideoMode::Color4bpp ? 32 : 16;
|
||||
int bpp = state.Mode >= WsVideoMode::Color4bpp ? 4 : 2;
|
||||
int paletteSize = state.Mode == WsVideoMode::Monochrome ? 4 : 16;
|
||||
int bank0Addr = state.Mode >= WsVideoMode::Color4bpp ? 0x4000 : 0x2000;
|
||||
int bank1Addr = state.Mode >= WsVideoMode::Color4bpp ? 0x8000 : 0x4000;
|
||||
|
||||
uint8_t colorMask = 0xFF;
|
||||
if(options.DisplayMode == TilemapDisplayMode::Grayscale) {
|
||||
palette = (uint32_t*)_grayscaleColorsBpp2;
|
||||
colorMask = 0x03;
|
||||
}
|
||||
|
||||
for(int row = 0; row < 32; row++) {
|
||||
uint16_t baseOffset = baseAddr + row * 64;
|
||||
|
||||
for(int column = 0; column < 32; column++) {
|
||||
uint16_t tilemapAddr = (baseOffset + column * 2);
|
||||
|
||||
uint16_t tilemapData = vram[tilemapAddr] | (vram[tilemapAddr + 1] << 8);
|
||||
uint16_t tileIndex = tilemapData & 0x1FF;
|
||||
uint8_t tilePalette = (tilemapData >> 9) & 0x0F;
|
||||
bool vMirror = tilemapData & 0x8000;
|
||||
bool hMirror = tilemapData & 0x4000;
|
||||
|
||||
uint16_t tilesetAddr = state.Mode >= WsVideoMode::Color2bpp && (tilemapData & 0x2000) ? bank1Addr : bank0Addr;
|
||||
uint16_t tileStart = tilesetAddr + tileIndex * tileSize;
|
||||
|
||||
for(int y = 0; y < 8; y++) {
|
||||
uint8_t tileRow = vMirror ? 7 - (y & 0x07) : (y & 0x07);
|
||||
uint16_t tileAddr = tileStart + tileRow * bpp;
|
||||
for(int x = 0; x < 8; x++) {
|
||||
uint8_t tileColumn = hMirror ? 7 - (x & 0x07) : (x & 0x07);
|
||||
uint8_t color;
|
||||
switch(state.Mode) {
|
||||
default:
|
||||
case WsVideoMode::Monochrome:
|
||||
case WsVideoMode::Color2bpp:
|
||||
color = GetTilePixelColor<TileFormat::Bpp2>(vram, ramMask, tileAddr, tileColumn);
|
||||
break;
|
||||
|
||||
case WsVideoMode::Color4bpp:
|
||||
color = GetTilePixelColor<TileFormat::SmsBpp4>(vram, ramMask, tileAddr, tileColumn);
|
||||
break;
|
||||
|
||||
case WsVideoMode::Color4bppPacked:
|
||||
color = GetTilePixelColor<TileFormat::WsBpp4Packed>(vram, ramMask, tileAddr, tileColumn);
|
||||
break;
|
||||
}
|
||||
|
||||
if(color != 0 || (!(tilePalette & 0x04) && state.Mode <= WsVideoMode::Color2bpp)) {
|
||||
uint32_t outPos = (row * 8 + y) * 256 + column * 8 + x;
|
||||
outBuffer[outPos] = palette[((tilePalette * paletteSize) + color) & colorMask];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DebugTilemapInfo result = {};
|
||||
result.Bpp = bpp;
|
||||
|
||||
switch(state.Mode) {
|
||||
case WsVideoMode::Monochrome:
|
||||
case WsVideoMode::Color2bpp:
|
||||
result.Format = TileFormat::Bpp2;
|
||||
break;
|
||||
|
||||
case WsVideoMode::Color4bpp:
|
||||
result.Format = TileFormat::SmsBpp4;
|
||||
break;
|
||||
|
||||
case WsVideoMode::Color4bppPacked:
|
||||
result.Format = TileFormat::WsBpp4Packed;
|
||||
break;
|
||||
}
|
||||
|
||||
result.TileWidth = 8;
|
||||
result.TileHeight = 8;
|
||||
result.ColumnCount = 32;
|
||||
result.RowCount = 32;
|
||||
result.TilemapAddress = baseAddr;
|
||||
result.TilesetAddress = bank0Addr;
|
||||
result.ScrollX = state.BgLayers[options.Layer].ScrollX;
|
||||
result.ScrollY = state.BgLayers[options.Layer].ScrollY;
|
||||
result.ScrollWidth = WsConstants::ScreenWidth;
|
||||
result.ScrollHeight = WsConstants::ScreenHeight;
|
||||
return result;
|
||||
}
|
||||
|
||||
DebugTilemapTileInfo WsPpuTools::GetTilemapTileInfo(uint32_t x, uint32_t y, uint8_t* vram, GetTilemapOptions options, BaseState& baseState)
|
||||
{
|
||||
DebugTilemapTileInfo result = {};
|
||||
|
||||
FrameInfo size = GetTilemapSize(options, baseState);
|
||||
if(x >= size.Width || y >= size.Height) {
|
||||
return result;
|
||||
}
|
||||
|
||||
WsPpuState& state = (WsPpuState&)baseState;
|
||||
|
||||
int tileSize = state.Mode >= WsVideoMode::Color4bpp ? 32 : 16;
|
||||
int bank0Addr = state.Mode >= WsVideoMode::Color4bpp ? 0x4000 : 0x2000;
|
||||
int bank1Addr = state.Mode >= WsVideoMode::Color4bpp ? 0x8000 : 0x4000;
|
||||
|
||||
int row = y / 8;
|
||||
int column = x / 8;
|
||||
|
||||
uint16_t baseAddr = state.BgLayers[options.Layer].MapAddress;
|
||||
|
||||
uint16_t tilemapAddr = (baseAddr + row * 64 + column * 2);
|
||||
|
||||
uint16_t tilemapData = vram[tilemapAddr] | (vram[tilemapAddr + 1] << 8);
|
||||
uint16_t tileIndex = tilemapData & 0x1FF;
|
||||
uint8_t tilePalette = (tilemapData >> 9) & 0x0F;
|
||||
bool vMirror = tilemapData & 0x8000;
|
||||
bool hMirror = tilemapData & 0x4000;
|
||||
|
||||
uint16_t tilesetAddr = state.Mode >= WsVideoMode::Color2bpp && (tilemapData & 0x2000) ? bank1Addr : bank0Addr;
|
||||
|
||||
result.Column = column;
|
||||
result.Row = row;
|
||||
result.Height = 8;
|
||||
result.Width = 8;
|
||||
result.TileMapAddress = tilemapAddr;
|
||||
result.TileIndex = tileIndex;
|
||||
result.TileAddress = tilesetAddr + tileIndex * tileSize;
|
||||
|
||||
result.PaletteIndex = tilePalette;
|
||||
result.PaletteAddress = state.Mode >= WsVideoMode::Color2bpp ? (0xFE00 | (result.PaletteIndex << 4)) : (result.PaletteIndex << 2);
|
||||
result.HorizontalMirroring = (NullableBoolean)hMirror;
|
||||
result.VerticalMirroring = (NullableBoolean)vMirror;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void WsPpuTools::GetSpritePreview(GetSpritePreviewOptions options, BaseState& baseState, DebugSpriteInfo* sprites, uint32_t* spritePreviews, uint32_t* palette, uint32_t* outBuffer)
|
||||
{
|
||||
uint32_t bgColor = GetSpriteBackgroundColor(options.Background, palette, false);
|
||||
|
||||
uint32_t darkBg = GetSpriteBackgroundColor(options.Background, palette, true);
|
||||
std::fill(outBuffer, outBuffer + 256 * WsConstants::ScreenHeight, bgColor);
|
||||
std::fill(outBuffer + 256 * WsConstants::ScreenHeight, outBuffer + 256 * 256, darkBg);
|
||||
for(int i = 0; i < WsConstants::ScreenHeight; i++) {
|
||||
std::fill(outBuffer + WsConstants::ScreenWidth + i * 256, outBuffer + 256 + i * 256 , darkBg);
|
||||
}
|
||||
|
||||
int spriteCount = 128;
|
||||
for(int i = spriteCount - 1; i >= 0; i--) {
|
||||
DebugSpriteInfo& sprite = sprites[i];
|
||||
uint32_t* spritePreview = spritePreviews + i * _spritePreviewSize;
|
||||
|
||||
int spritePosY = sprite.Y;
|
||||
|
||||
for(int y = 0; y < sprite.Height; y++) {
|
||||
for(int x = 0; x < sprite.Width; x++) {
|
||||
if(spritePosY + y >= 256) {
|
||||
spritePosY -= 256;
|
||||
}
|
||||
|
||||
uint32_t color = spritePreview[y * sprite.Width + x];
|
||||
if(color != 0) {
|
||||
if(sprite.X + x >= 256 || sprite.Visibility == SpriteVisibility::Disabled) {
|
||||
continue;
|
||||
}
|
||||
|
||||
outBuffer[((spritePosY + y) * 256) + sprite.X + x] = color;
|
||||
} else {
|
||||
spritePreview[y * sprite.Width + x] = bgColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DebugSpritePreviewInfo WsPpuTools::GetSpritePreviewInfo(GetSpritePreviewOptions options, BaseState& baseState)
|
||||
{
|
||||
DebugSpritePreviewInfo info = {};
|
||||
info.Height = 256;
|
||||
info.Width = 256;
|
||||
info.SpriteCount = 128;
|
||||
info.CoordOffsetX = 0;
|
||||
info.CoordOffsetY = 0;
|
||||
info.WrapBottomToTop = true;
|
||||
info.WrapRightToLeft = true;
|
||||
|
||||
info.VisibleX = 0;
|
||||
info.VisibleY = 0;
|
||||
info.VisibleWidth = WsConstants::ScreenWidth;
|
||||
info.VisibleHeight = WsConstants::ScreenHeight;
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
void WsPpuTools::GetSpriteInfo(DebugSpriteInfo& sprite, uint32_t* spritePreview, uint16_t i, GetSpritePreviewOptions& options, WsPpuState& state, uint8_t* vram, uint8_t* oamRam, uint32_t* palette)
|
||||
{
|
||||
uint16_t ramMask = state.Mode == WsVideoMode::Monochrome ? 0x3FFF : 0xFFFF;
|
||||
|
||||
uint8_t* oam = oamRam ? oamRam : (vram + state.SpriteTableAddress);
|
||||
|
||||
int tileSize = state.Mode >= WsVideoMode::Color4bpp ? 32 : 16;
|
||||
int bpp = state.Mode >= WsVideoMode::Color4bpp ? 4 : 2;
|
||||
int bank0Addr = state.Mode >= WsVideoMode::Color4bpp ? 0x4000 : 0x2000;
|
||||
|
||||
sprite.Bpp = bpp;
|
||||
|
||||
switch(state.Mode) {
|
||||
case WsVideoMode::Monochrome:
|
||||
case WsVideoMode::Color2bpp:
|
||||
sprite.Format = TileFormat::Bpp2;
|
||||
break;
|
||||
|
||||
case WsVideoMode::Color4bpp:
|
||||
sprite.Format = TileFormat::SmsBpp4;
|
||||
break;
|
||||
|
||||
case WsVideoMode::Color4bppPacked:
|
||||
sprite.Format = TileFormat::WsBpp4Packed;
|
||||
break;
|
||||
}
|
||||
|
||||
sprite.SpriteIndex = i;
|
||||
sprite.UseExtendedVram = false;
|
||||
sprite.RawY = oam[i*4 + 2];
|
||||
sprite.RawX = oam[i*4 + 3];
|
||||
sprite.Y = sprite.RawY;
|
||||
sprite.X = sprite.RawX;
|
||||
sprite.UseSecondTable = NullableBoolean::Undefined;
|
||||
|
||||
uint8_t attributes = oam[i*4 + 1];
|
||||
bool highPriority = attributes & 0x20;
|
||||
|
||||
bool vMirror = attributes & 0x80;
|
||||
bool hMirror = attributes & 0x40;
|
||||
|
||||
uint16_t tileIndex = oam[i*4] | ((attributes & 0x01) << 8);
|
||||
sprite.TileIndex = tileIndex;
|
||||
uint8_t sprPalette = ((attributes >> 1) & 0x07);
|
||||
int paletteSize = state.Mode == WsVideoMode::Monochrome ? 4 : 16;
|
||||
|
||||
sprite.Palette = sprPalette;
|
||||
sprite.PaletteAddress = (8 + sprPalette) * paletteSize;
|
||||
sprite.Priority = highPriority ? DebugSpritePriority::Foreground : DebugSpritePriority::Background;
|
||||
|
||||
sprite.Width = 8;
|
||||
sprite.Height = 8;
|
||||
|
||||
if(i < state.FirstSpriteIndex || i >= state.FirstSpriteIndex + state.SpriteCount) {
|
||||
sprite.Visibility = SpriteVisibility::Disabled;
|
||||
} else if(sprite.Y < WsConstants::ScreenHeight && sprite.X < WsConstants::ScreenWidth) {
|
||||
sprite.Visibility = SpriteVisibility::Visible;
|
||||
} else {
|
||||
sprite.Visibility = SpriteVisibility::Offscreen;
|
||||
}
|
||||
|
||||
uint16_t tilesetAddr = bank0Addr;
|
||||
uint16_t tileStart = tilesetAddr + (sprite.TileIndex * tileSize);
|
||||
sprite.TileAddresses[0] = tileStart;
|
||||
sprite.TileCount = 1;
|
||||
sprite.TileAddress = tileStart;
|
||||
|
||||
for(int y = 0; y < 8; y++) {
|
||||
uint8_t yPos = vMirror ? 7 - y : y;
|
||||
uint16_t pixelStart = tileStart + yPos * bpp;
|
||||
for(int x = 0; x < 8; x++) {
|
||||
uint8_t color;
|
||||
uint8_t xPos = hMirror ? 7 - x : x;
|
||||
switch(state.Mode) {
|
||||
default:
|
||||
case WsVideoMode::Monochrome:
|
||||
case WsVideoMode::Color2bpp:
|
||||
color = GetTilePixelColor<TileFormat::Bpp2>(vram, ramMask, pixelStart, xPos);
|
||||
break;
|
||||
|
||||
case WsVideoMode::Color4bpp:
|
||||
color = GetTilePixelColor<TileFormat::SmsBpp4>(vram, ramMask, pixelStart, xPos);
|
||||
break;
|
||||
|
||||
case WsVideoMode::Color4bppPacked:
|
||||
color = GetTilePixelColor<TileFormat::WsBpp4Packed>(vram, ramMask, pixelStart, xPos);
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t outOffset = (y * 8) + x;
|
||||
if(color > 0 || (!(sprPalette & 0x04) && state.Mode <= WsVideoMode::Color2bpp)) {
|
||||
spritePreview[outOffset] = palette[sprite.PaletteAddress + color];
|
||||
} else {
|
||||
spritePreview[outOffset] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WsPpuTools::GetSpriteList(GetSpritePreviewOptions options, BaseState& baseState, uint8_t* vram, uint8_t* oamRam, uint32_t* palette, DebugSpriteInfo outBuffer[], uint32_t* spritePreviews, uint32_t* screenPreview)
|
||||
{
|
||||
WsPpuState& state = (WsPpuState&)baseState;
|
||||
int spriteCount = 128;
|
||||
|
||||
for(int i = 0; i < spriteCount; i++) {
|
||||
outBuffer[i].Init();
|
||||
GetSpriteInfo(outBuffer[i], spritePreviews + (i * _spritePreviewSize), i, options, state, vram, oamRam, palette);
|
||||
}
|
||||
|
||||
GetSpritePreview(options, baseState, outBuffer, spritePreviews, palette, screenPreview);
|
||||
}
|
||||
|
||||
DebugPaletteInfo WsPpuTools::GetPaletteInfo(GetPaletteInfoOptions options)
|
||||
{
|
||||
DebugPaletteInfo info = {};
|
||||
|
||||
WsPpuState state;
|
||||
_debugger->GetPpuState(state, CpuType::Ws);
|
||||
|
||||
if(state.Mode == WsVideoMode::Monochrome) {
|
||||
info.ColorCount = 64;
|
||||
info.BgColorCount = 32;
|
||||
info.SpriteColorCount = 32;
|
||||
info.ColorsPerPalette = 4;
|
||||
info.SpritePaletteOffset = info.BgColorCount;
|
||||
|
||||
info.RawFormat = RawPaletteFormat::Indexed;
|
||||
for(int i = 0; i < 64; i++) {
|
||||
info.RawPalette[i] = state.BwPalettes[i];
|
||||
uint8_t brightness = state.BwShades[state.BwPalettes[i]] ^ 0x0F;
|
||||
info.RgbPalette[i] = ColorUtilities::Rgb444ToArgb(brightness | (brightness << 4) | (brightness << 8));
|
||||
}
|
||||
} else {
|
||||
info.ColorCount = 256;
|
||||
info.BgColorCount = 128;
|
||||
info.SpriteColorCount = 128;
|
||||
info.ColorsPerPalette = 16;
|
||||
info.SpritePaletteOffset = info.BgColorCount;
|
||||
|
||||
info.HasMemType = true;
|
||||
info.PaletteMemType = MemoryType::WsWorkRam;
|
||||
info.PaletteMemOffset = 0xFE00;
|
||||
|
||||
uint8_t* palette = _debugger->GetMemoryDumper()->GetMemoryBuffer(MemoryType::WsWorkRam);
|
||||
|
||||
info.RawFormat = RawPaletteFormat::Rgb444;
|
||||
for(int i = 0; i < 256; i++) {
|
||||
int addr = 0xFE00 + i * 2;
|
||||
info.RawPalette[i] = palette[addr] | ((palette[addr + 1] & 0x0F) << 8);
|
||||
info.RgbPalette[i] = ColorUtilities::Bgr444ToArgb(info.RawPalette[i]);
|
||||
}
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
void WsPpuTools::SetPaletteColor(int32_t colorIndex, uint32_t color)
|
||||
{
|
||||
WsPpuState state;
|
||||
_debugger->GetPpuState(state, CpuType::Ws);
|
||||
|
||||
if(state.Mode == WsVideoMode::Monochrome) {
|
||||
_console->GetPpu()->GetState().BwPalettes[colorIndex] = color;
|
||||
} else {
|
||||
uint8_t r = (color >> 20) & 0x0F;
|
||||
uint8_t g = (color >> 12) & 0x0F;
|
||||
uint8_t b = (color >> 4) & 0x0F;
|
||||
|
||||
uint16_t rgb444 = (r << 8) | (g << 4) | b;
|
||||
_debugger->GetMemoryDumper()->SetMemoryValue(MemoryType::WsWorkRam, 0xFE00 + colorIndex * 2, rgb444 & 0xFF);
|
||||
_debugger->GetMemoryDumper()->SetMemoryValue(MemoryType::WsWorkRam, 0xFE00 + colorIndex * 2 + 1, (rgb444 >> 8));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
#include "pch.h"
|
||||
#include "Debugger/PpuTools.h"
|
||||
|
||||
class Debugger;
|
||||
class Emulator;
|
||||
class WsConsole;
|
||||
struct WsPpuState;
|
||||
|
||||
class WsPpuTools final : public PpuTools
|
||||
{
|
||||
private:
|
||||
WsConsole* _console = nullptr;
|
||||
|
||||
void GetSpriteInfo(DebugSpriteInfo& sprite, uint32_t* spritePreview, uint16_t spriteIndex, GetSpritePreviewOptions& options, WsPpuState& state, uint8_t* vram, uint8_t* oamRam, uint32_t* palette);
|
||||
void GetSpritePreview(GetSpritePreviewOptions options, BaseState& state, DebugSpriteInfo* sprites, uint32_t* spritePreviews, uint32_t* palette, uint32_t *outBuffer);
|
||||
|
||||
public:
|
||||
WsPpuTools(Debugger* debugger, Emulator *emu, WsConsole* console);
|
||||
|
||||
DebugTilemapInfo GetTilemap(GetTilemapOptions options, BaseState& state, uint8_t* vram, uint32_t* palette, uint32_t *outBuffer) override;
|
||||
FrameInfo GetTilemapSize(GetTilemapOptions options, BaseState& state) override;
|
||||
DebugTilemapTileInfo GetTilemapTileInfo(uint32_t x, uint32_t y, uint8_t* vram, GetTilemapOptions options, BaseState& baseState) override;
|
||||
|
||||
DebugSpritePreviewInfo GetSpritePreviewInfo(GetSpritePreviewOptions options, BaseState& state) override;
|
||||
void GetSpriteList(GetSpritePreviewOptions options, BaseState& baseState, uint8_t* vram, uint8_t* oamRam, uint32_t* palette, DebugSpriteInfo outBuffer[], uint32_t* spritePreviews, uint32_t* screenPreview) override;
|
||||
|
||||
DebugPaletteInfo GetPaletteInfo(GetPaletteInfoOptions options) override;
|
||||
void SetPaletteColor(int32_t colorIndex, uint32_t color) override;
|
||||
};
|
||||
@@ -0,0 +1,104 @@
|
||||
#include "pch.h"
|
||||
#include "WS/Debugger/WsTraceLogger.h"
|
||||
#include "WS/WsPpu.h"
|
||||
#include "WS/WsTypes.h"
|
||||
#include "Debugger/DisassemblyInfo.h"
|
||||
#include "Debugger/Debugger.h"
|
||||
#include "Debugger/DebugTypes.h"
|
||||
#include "Utilities/HexUtilities.h"
|
||||
#include "Utilities/FastString.h"
|
||||
|
||||
WsTraceLogger::WsTraceLogger(Debugger* debugger, IDebugger* cpuDebugger, WsPpu* ppu) : BaseTraceLogger(debugger, cpuDebugger, CpuType::Ws)
|
||||
{
|
||||
_ppu = ppu;
|
||||
}
|
||||
|
||||
RowDataType WsTraceLogger::GetFormatTagType(string& tag)
|
||||
{
|
||||
if(tag == "AX") {
|
||||
return RowDataType::AX;
|
||||
} else if(tag == "BX") {
|
||||
return RowDataType::BX;
|
||||
} else if(tag == "CX") {
|
||||
return RowDataType::CX;
|
||||
} else if(tag == "DX") {
|
||||
return RowDataType::DX;
|
||||
} else if(tag == "CS") {
|
||||
return RowDataType::CS;
|
||||
} else if(tag == "IP") {
|
||||
return RowDataType::IP;
|
||||
} else if(tag == "SS") {
|
||||
return RowDataType::SS;
|
||||
} else if(tag == "SP") {
|
||||
return RowDataType::SP;
|
||||
} else if(tag == "BP") {
|
||||
return RowDataType::BP;
|
||||
} else if(tag == "DS") {
|
||||
return RowDataType::DS;
|
||||
} else if(tag == "ES") {
|
||||
return RowDataType::ES;
|
||||
} else if(tag == "SI") {
|
||||
return RowDataType::SI;
|
||||
} else if(tag == "DI") {
|
||||
return RowDataType::DI;
|
||||
} else if(tag == "F") {
|
||||
return RowDataType::F;
|
||||
} else if(tag == "PC") {
|
||||
return RowDataType::PC;
|
||||
} else {
|
||||
return RowDataType::Text;
|
||||
}
|
||||
}
|
||||
|
||||
void WsTraceLogger::GetTraceRow(string &output, WsCpuState &cpuState, TraceLogPpuState &ppuState, DisassemblyInfo &disassemblyInfo)
|
||||
{
|
||||
for(RowPart& rowPart : _rowParts) {
|
||||
switch(rowPart.DataType) {
|
||||
case RowDataType::AX: WriteIntValue(output, cpuState.AX, rowPart); break;
|
||||
case RowDataType::BX: WriteIntValue(output, cpuState.BX, rowPart); break;
|
||||
case RowDataType::CX: WriteIntValue(output, cpuState.CX, rowPart); break;
|
||||
case RowDataType::DX: WriteIntValue(output, cpuState.DX, rowPart); break;
|
||||
case RowDataType::CS: WriteIntValue(output, cpuState.CS, rowPart); break;
|
||||
case RowDataType::IP: WriteIntValue(output, cpuState.IP, rowPart); break;
|
||||
case RowDataType::SS: WriteIntValue(output, cpuState.SS, rowPart); break;
|
||||
case RowDataType::SP: WriteIntValue(output, cpuState.SP, rowPart); break;
|
||||
case RowDataType::BP: WriteIntValue(output, cpuState.BP, rowPart); break;
|
||||
case RowDataType::DS: WriteIntValue(output, cpuState.DS, rowPart); break;
|
||||
case RowDataType::ES: WriteIntValue(output, cpuState.ES, rowPart); break;
|
||||
case RowDataType::SI: WriteIntValue(output, cpuState.SI, rowPart); break;
|
||||
case RowDataType::DI: WriteIntValue(output, cpuState.DI, rowPart); break;
|
||||
case RowDataType::F:
|
||||
if(rowPart.DisplayInHex) {
|
||||
WriteIntValue(output, cpuState.Flags.Get(), rowPart);
|
||||
} else {
|
||||
FastString flags;
|
||||
|
||||
bool showInactive = (rowPart.MinWidth > 0);
|
||||
flags.Write(cpuState.Flags.Carry ? "C" : (showInactive ? "c" : ""));
|
||||
flags.Write(cpuState.Flags.Parity ? "P" : (showInactive ? "p" : ""));
|
||||
flags.Write(cpuState.Flags.AuxCarry ? "A" : (showInactive ? "a" : ""));
|
||||
flags.Write(cpuState.Flags.Zero ? "Z" : (showInactive ? "z" : ""));
|
||||
flags.Write(cpuState.Flags.Sign ? "S" : (showInactive ? "s" : ""));
|
||||
flags.Write(cpuState.Flags.Trap ? "T" : (showInactive ? "t" : ""));
|
||||
flags.Write(cpuState.Flags.Irq ? "I" : (showInactive ? "i" : ""));
|
||||
flags.Write(cpuState.Flags.Direction ? "D" : (showInactive ? "d" : ""));
|
||||
flags.Write(cpuState.Flags.Overflow ? "O" : (showInactive ? "o" : ""));
|
||||
flags.Write(cpuState.Flags.Mode ? "M" : (showInactive ? "m" : ""));
|
||||
WriteStringValue(output, flags.ToString(), rowPart);
|
||||
}
|
||||
break;
|
||||
|
||||
default: ProcessSharedTag(rowPart, output, cpuState, ppuState, disassemblyInfo); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WsTraceLogger::LogPpuState()
|
||||
{
|
||||
_ppuState[_currentPos] = {
|
||||
_ppu->GetCycle(),
|
||||
_ppu->GetCycle(),
|
||||
_ppu->GetScanline(),
|
||||
_ppu->GetFrameCount()
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
#include "pch.h"
|
||||
#include "Debugger/BaseTraceLogger.h"
|
||||
#include "WS/WsTypes.h"
|
||||
|
||||
class DisassemblyInfo;
|
||||
class Debugger;
|
||||
class WsPpu;
|
||||
|
||||
class WsTraceLogger : public BaseTraceLogger<WsTraceLogger, WsCpuState>
|
||||
{
|
||||
private:
|
||||
WsPpu* _ppu = nullptr;
|
||||
|
||||
protected:
|
||||
RowDataType GetFormatTagType(string& tag) override;
|
||||
|
||||
public:
|
||||
WsTraceLogger(Debugger* debugger, IDebugger* cpuDebugger, WsPpu* ppu);
|
||||
|
||||
void GetTraceRow(string& output, WsCpuState& cpuState, TraceLogPpuState& ppuState, DisassemblyInfo& disassemblyInfo);
|
||||
void LogPpuState();
|
||||
|
||||
__forceinline uint32_t GetProgramCounter(WsCpuState& state) { return (state.CS << 4) + state.IP; }
|
||||
__forceinline uint64_t GetCycleCount(WsCpuState& state) { return state.CycleCount; }
|
||||
__forceinline uint8_t GetStackPointer(WsCpuState& state) { return (uint8_t)state.SP; }
|
||||
};
|
||||
@@ -0,0 +1,472 @@
|
||||
#include "pch.h"
|
||||
#include "WS/WsConsole.h"
|
||||
#include "WS/WsCpu.h"
|
||||
#include "WS/WsPpu.h"
|
||||
#include "WS/WsTimer.h"
|
||||
#include "WS/Carts/WsCart.h"
|
||||
#include "WS/WsControlManager.h"
|
||||
#include "WS/WsMemoryManager.h"
|
||||
#include "WS/WsDmaController.h"
|
||||
#include "WS/WsEeprom.h"
|
||||
#include "WS/WsSerial.h"
|
||||
#include "WS/WsDefaultVideoFilter.h"
|
||||
#include "WS/APU/WsApu.h"
|
||||
#include "Shared/CpuType.h"
|
||||
#include "Shared/Emulator.h"
|
||||
#include "Shared/SettingTypes.h"
|
||||
#include "Shared/FirmwareHelper.h"
|
||||
#include "Shared/BatteryManager.h"
|
||||
|
||||
WsConsole::WsConsole(Emulator* emu)
|
||||
{
|
||||
_emu = emu;
|
||||
}
|
||||
|
||||
WsConsole::~WsConsole()
|
||||
{
|
||||
delete[] _workRam;
|
||||
delete[] _saveRam;
|
||||
delete[] _prgRom;
|
||||
delete[] _bootRom;
|
||||
delete[] _internalEepromData;
|
||||
delete[] _cartEepromData;
|
||||
}
|
||||
|
||||
LoadRomResult WsConsole::LoadRom(VirtualFile& romFile)
|
||||
{
|
||||
vector<uint8_t> romData;
|
||||
romFile.ReadFile(romData);
|
||||
|
||||
if(romData.size() < 0x10000) {
|
||||
return LoadRomResult::Failure;
|
||||
}
|
||||
|
||||
MessageManager::Log("------------------------------");
|
||||
|
||||
_prgRomSize = (uint32_t)romData.size();
|
||||
_prgRom = new uint8_t[_prgRomSize];
|
||||
memcpy(_prgRom, romData.data(), _prgRomSize);
|
||||
_emu->RegisterMemory(MemoryType::WsPrgRom, _prgRom, _prgRomSize);
|
||||
|
||||
uint8_t sramType = _prgRom[_prgRomSize - 5];
|
||||
switch(sramType) {
|
||||
case 0x00: _saveRamSize = 0; break;
|
||||
case 0x01: _saveRamSize = 32 * 1024; break;
|
||||
case 0x02: _saveRamSize = 32 * 1024; break;
|
||||
case 0x03: _saveRamSize = 128 * 1024; break;
|
||||
case 0x04: _saveRamSize = 256 * 1024; break;
|
||||
case 0x05: _saveRamSize = 512 * 1024; break;
|
||||
|
||||
case 0x10: _cartEepromSize = 0x80; break;
|
||||
case 0x20: _cartEepromSize = 0x800; break;
|
||||
case 0x50: _cartEepromSize = 0x400; break;
|
||||
|
||||
default: MessageManager::Log("Save RAM: unrecognized value"); break;
|
||||
}
|
||||
|
||||
bool hasColorSupport = (_prgRom[_prgRomSize - 9] & 0x01) || romFile.GetFileExtension() == ".wsc";
|
||||
uint8_t mapperType = _prgRom[_prgRomSize - 3];
|
||||
|
||||
MessageManager::Log(string("Color supported: ") + (hasColorSupport ? "Yes" : "No"));
|
||||
MessageManager::Log("Save RAM size: " + std::to_string(_saveRamSize / 1024) + " KB");
|
||||
MessageManager::Log("Cart EEPROM size: " + std::to_string(_cartEepromSize) + " bytes");
|
||||
MessageManager::Log(string("Mapper: ") + (mapperType == 0 ? "Bandai 2001 / KARNAK" : (mapperType == 1 ? "Bandai 2003" : ("Unknown: " + std::to_string(mapperType)))));
|
||||
|
||||
MessageManager::Log("------------------------------");
|
||||
|
||||
if(_saveRamSize > 0) {
|
||||
_saveRam = new uint8_t[_saveRamSize];
|
||||
memset(_saveRam, 0, _saveRamSize);
|
||||
_emu->RegisterMemory(MemoryType::WsCartRam, _saveRam, _saveRamSize);
|
||||
}
|
||||
|
||||
if(_cartEepromSize > 0) {
|
||||
_cartEepromData = new uint8_t[_cartEepromSize];
|
||||
memset(_cartEepromData, 0, _cartEepromSize);
|
||||
_emu->RegisterMemory(MemoryType::WsCartEeprom, _cartEepromData, _cartEepromSize);
|
||||
_cartEeprom.reset(new WsEeprom(_emu, this, (WsEepromSize)_cartEepromSize, _cartEepromData, false));
|
||||
}
|
||||
|
||||
_model = _emu->GetSettings()->GetWsConfig().Model;
|
||||
if(_model == WsModel::Auto) {
|
||||
_model = hasColorSupport ? WsModel::Color : WsModel::Monochrome;
|
||||
}
|
||||
|
||||
_workRamSize = _model == WsModel::Monochrome ? 0x4000 : 0x10000;
|
||||
_workRam = new uint8_t[_workRamSize];
|
||||
memset(_workRam, 0, _workRamSize);
|
||||
_emu->RegisterMemory(MemoryType::WsWorkRam, _workRam, _workRamSize);
|
||||
|
||||
vector<uint8_t> bootRom;
|
||||
if(_emu->GetSettings()->GetWsConfig().UseBootRom && FirmwareHelper::LoadWsBootRom(_emu, bootRom, _model)) {
|
||||
_bootRomSize = (uint32_t)bootRom.size();
|
||||
_bootRom = new uint8_t[_bootRomSize];
|
||||
memcpy(_bootRom, bootRom.data(), _bootRomSize);
|
||||
_emu->RegisterMemory(MemoryType::WsBootRom, _bootRom, _bootRomSize);
|
||||
}
|
||||
|
||||
_internalEepromSize = (uint32_t)(_model == WsModel::Monochrome ? WsEepromSize::Size128 : WsEepromSize::Size2kb);
|
||||
_internalEepromData = new uint8_t[_internalEepromSize];
|
||||
memset(_internalEepromData, 0, _internalEepromSize);
|
||||
_emu->RegisterMemory(MemoryType::WsInternalEeprom, _internalEepromData, _internalEepromSize);
|
||||
|
||||
_internalEeprom.reset(new WsEeprom(_emu, this, (WsEepromSize)_internalEepromSize, _internalEepromData, true));
|
||||
|
||||
_controlManager.reset(new WsControlManager(_emu, this));
|
||||
_memoryManager.reset(new WsMemoryManager());
|
||||
_cpu.reset(new WsCpu(_emu, _memoryManager.get()));
|
||||
_timer.reset(new WsTimer());
|
||||
_serial.reset(new WsSerial(this));
|
||||
_dmaController.reset(new WsDmaController());
|
||||
_ppu.reset(new WsPpu(_emu, this, _timer.get(), _workRam));
|
||||
_apu.reset(new WsApu(_emu, this, _memoryManager.get(), _dmaController.get()));
|
||||
_cart.reset(new WsCart());
|
||||
|
||||
_cart->Init(_memoryManager.get(), _cartEeprom.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());
|
||||
|
||||
if(!_bootRom) {
|
||||
InitPostBootRomState();
|
||||
}
|
||||
|
||||
LoadBattery();
|
||||
|
||||
return LoadRomResult::Success;
|
||||
}
|
||||
|
||||
void WsConsole::RunFrame()
|
||||
{
|
||||
uint32_t frameCount = _ppu->GetFrameCount();
|
||||
while(frameCount == _ppu->GetFrameCount()) {
|
||||
_cpu->Exec();
|
||||
}
|
||||
|
||||
_apu->PlayQueuedAudio();
|
||||
|
||||
_verticalMode |= _ppu->GetState().Icons.Vertical;
|
||||
if(_ppu->GetState().Icons.Horizontal) {
|
||||
_verticalMode = false;
|
||||
}
|
||||
}
|
||||
|
||||
void WsConsole::GetScreenRotationOverride(uint32_t& rotation)
|
||||
{
|
||||
if(_emu->GetSettings()->GetWsConfig().AutoRotate) {
|
||||
if(_verticalMode) {
|
||||
rotation = 270;
|
||||
} else {
|
||||
rotation = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool WsConsole::IsColorMode()
|
||||
{
|
||||
return _memoryManager->GetState().ColorEnabled;
|
||||
}
|
||||
|
||||
bool WsConsole::IsVerticalMode()
|
||||
{
|
||||
return _verticalMode;
|
||||
}
|
||||
|
||||
WsModel WsConsole::GetModel()
|
||||
{
|
||||
return _model;
|
||||
}
|
||||
|
||||
void WsConsole::ProcessEndOfFrame()
|
||||
{
|
||||
_controlManager->UpdateControlDevices();
|
||||
_controlManager->UpdateInputState();
|
||||
}
|
||||
|
||||
void WsConsole::Reset()
|
||||
{
|
||||
//The WS has no reset button, behave like power cycle
|
||||
_emu->ReloadRom(true);
|
||||
}
|
||||
|
||||
void WsConsole::InitPostBootRomState()
|
||||
{
|
||||
//Init work ram
|
||||
if(_model == WsModel::Monochrome) {
|
||||
memset(_workRam, 0, _workRamSize);
|
||||
} else {
|
||||
memset(_workRam, 0, 0xFE00);
|
||||
memset(_workRam + 0xFE00, 0xFF, 0x200);
|
||||
}
|
||||
|
||||
//Init port state
|
||||
WsCpuState& cpu = _cpu->GetState();
|
||||
cpu.DS = 0xFE00;
|
||||
cpu.CS = 0xFFFF;
|
||||
cpu.SP = 0x2000;
|
||||
|
||||
if(_model == WsModel::Monochrome) {
|
||||
cpu.AX = 0xFF85;
|
||||
cpu.BX = 0x0040;
|
||||
cpu.DX = 0x0005;
|
||||
cpu.SI = 0x023D;
|
||||
cpu.DI = 0x040D;
|
||||
cpu.Flags.Sign = true;
|
||||
} else {
|
||||
cpu.AX = 0xFF87;
|
||||
cpu.BX = 0x0043;
|
||||
cpu.DX = 0x0001;
|
||||
cpu.SI = 0x0435;
|
||||
cpu.DI = 0x040B;
|
||||
cpu.Flags.Sign = true;
|
||||
cpu.Flags.Parity = true;
|
||||
}
|
||||
|
||||
WsPpuState& ppu = _ppu->GetState();
|
||||
ppu.LcdEnabled = true;
|
||||
if(_model == WsModel::Monochrome) {
|
||||
ppu.Scanline = 26;
|
||||
ppu.Cycle = 53;
|
||||
|
||||
constexpr static uint8_t defaultBwPalette[] = {
|
||||
0,0,0,7,0,0,0,5,0,0,0,4,0,0,0,3,
|
||||
0,0,0,2,0,0,0,1,0,0,0,0,0,0,0,0,
|
||||
0,3,5,7,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
};
|
||||
|
||||
constexpr static uint8_t defaultShades[] = { 0,2,4,6,8,10,12,15 };
|
||||
|
||||
memcpy(ppu.BwPalettes, defaultBwPalette, sizeof(defaultBwPalette));
|
||||
memcpy(ppu.BwShades, defaultShades, sizeof(defaultShades));
|
||||
} else {
|
||||
ppu.Scanline = 127;
|
||||
ppu.Cycle = 232;
|
||||
}
|
||||
|
||||
_ppu->SetOutputToBgColor();
|
||||
|
||||
WsMemoryManagerState& mm = _memoryManager->GetState();
|
||||
mm.BootRomDisabled = true;
|
||||
mm.CartWordBus = true;
|
||||
if(_model != WsModel::Monochrome) {
|
||||
mm.SlowSram = true;
|
||||
mm.SlowPort = true;
|
||||
}
|
||||
|
||||
WsApuState& apu = _apu->GetState();
|
||||
if(_model == WsModel::Monochrome) {
|
||||
apu.Ch1.Frequency = 0x7E6;
|
||||
apu.Ch2.Frequency = 0x7E6;
|
||||
} else {
|
||||
apu.InternalMasterVolume = _internalEepromData[0x83] & 0x03;
|
||||
ppu.HighContrast = (_internalEepromData[0x83] & 0x40);
|
||||
}
|
||||
|
||||
WsEepromState& eeprom = _internalEeprom->GetState();
|
||||
eeprom.ReadDone = true;
|
||||
eeprom.Idle = true;
|
||||
eeprom.InternalEepromWriteProtected = true;
|
||||
|
||||
//Copy data from rom header to internal eeprom, like the boot rom would
|
||||
for(int i = 0; i < 3; i++) {
|
||||
_internalEepromData[0x76 + i] = _prgRom[_prgRomSize - 0x10 + 6 + i];
|
||||
|
||||
bool supportsColor = _prgRom[_prgRomSize - 0x10 + 7] & 0x01;
|
||||
if(_model != WsModel::Monochrome && supportsColor) {
|
||||
//For games that support color & color models, copy 76-78 to 80-82
|
||||
_internalEepromData[0x80 + i] = _internalEepromData[0x76 + i];
|
||||
}
|
||||
}
|
||||
|
||||
_memoryManager->RefreshMappings();
|
||||
}
|
||||
|
||||
void WsConsole::LoadBattery()
|
||||
{
|
||||
_internalEeprom->LoadBattery();
|
||||
if(_cartEeprom) {
|
||||
_cartEeprom->LoadBattery();
|
||||
}
|
||||
|
||||
if(_saveRam) {
|
||||
_emu->GetBatteryManager()->LoadBattery(".sav", _saveRam, _saveRamSize);
|
||||
}
|
||||
}
|
||||
|
||||
void WsConsole::SaveBattery()
|
||||
{
|
||||
_internalEeprom->SaveBattery();
|
||||
if(_cartEeprom) {
|
||||
_cartEeprom->SaveBattery();
|
||||
}
|
||||
|
||||
if(_saveRam) {
|
||||
_emu->GetBatteryManager()->SaveBattery(".sav", _saveRam, _saveRamSize);
|
||||
}
|
||||
}
|
||||
|
||||
BaseControlManager* WsConsole::GetControlManager()
|
||||
{
|
||||
return _controlManager.get();
|
||||
}
|
||||
|
||||
ConsoleRegion WsConsole::GetRegion()
|
||||
{
|
||||
return ConsoleRegion::Ntsc;
|
||||
}
|
||||
|
||||
ConsoleType WsConsole::GetConsoleType()
|
||||
{
|
||||
return ConsoleType::Ws;
|
||||
}
|
||||
|
||||
vector<CpuType> WsConsole::GetCpuTypes()
|
||||
{
|
||||
return { CpuType::Ws };
|
||||
}
|
||||
|
||||
uint64_t WsConsole::GetMasterClock()
|
||||
{
|
||||
return _cpu->GetCycleCount();
|
||||
}
|
||||
|
||||
uint32_t WsConsole::GetMasterClockRate()
|
||||
{
|
||||
return 12288000 / 4;
|
||||
}
|
||||
|
||||
double WsConsole::GetFps()
|
||||
{
|
||||
return 12000.0 / _ppu->GetScanlineCount();
|
||||
}
|
||||
|
||||
BaseVideoFilter* WsConsole::GetVideoFilter(bool getDefaultFilter)
|
||||
{
|
||||
if(getDefaultFilter) {
|
||||
return new WsDefaultVideoFilter(_emu, this, false);
|
||||
}
|
||||
|
||||
VideoFilterType filterType = _emu->GetSettings()->GetVideoConfig().VideoFilter;
|
||||
|
||||
switch(filterType) {
|
||||
case VideoFilterType::NtscBisqwit:
|
||||
case VideoFilterType::NtscBlargg:
|
||||
return new WsDefaultVideoFilter(_emu, this, true);
|
||||
|
||||
default: return new WsDefaultVideoFilter(_emu, this, false);
|
||||
}
|
||||
}
|
||||
|
||||
PpuFrameInfo WsConsole::GetPpuFrame()
|
||||
{
|
||||
PpuFrameInfo frame = {};
|
||||
frame.FirstScanline = 0;
|
||||
frame.FrameCount = _ppu->GetFrameCount();
|
||||
frame.Width = _ppu->GetScreenWidth();
|
||||
frame.Height = _ppu->GetScreenHeight();
|
||||
frame.ScanlineCount = WsConstants::ScanlineCount;
|
||||
frame.CycleCount = WsConstants::ClocksPerScanline;
|
||||
frame.FrameBufferSize = frame.Width * frame.Height * sizeof(uint16_t);
|
||||
frame.FrameBuffer = (uint8_t*)_ppu->GetScreenBuffer(false);
|
||||
return frame;
|
||||
}
|
||||
|
||||
RomFormat WsConsole::GetRomFormat()
|
||||
{
|
||||
return RomFormat::Ws;
|
||||
}
|
||||
|
||||
AudioTrackInfo WsConsole::GetAudioTrackInfo()
|
||||
{
|
||||
//TODOWS
|
||||
return AudioTrackInfo();
|
||||
}
|
||||
|
||||
void WsConsole::ProcessAudioPlayerAction(AudioPlayerActionParams p)
|
||||
{
|
||||
//TODOWS
|
||||
}
|
||||
|
||||
AddressInfo WsConsole::GetAbsoluteAddress(uint32_t relAddress)
|
||||
{
|
||||
return _memoryManager->GetAbsoluteAddress(relAddress);
|
||||
}
|
||||
|
||||
AddressInfo WsConsole::GetAbsoluteAddress(AddressInfo& relAddress)
|
||||
{
|
||||
return GetAbsoluteAddress(relAddress.Address);
|
||||
}
|
||||
|
||||
AddressInfo WsConsole::GetRelativeAddress(AddressInfo& absAddress, CpuType cpuType)
|
||||
{
|
||||
return { _memoryManager->GetRelativeAddress(absAddress), MemoryType::WsMemory };
|
||||
}
|
||||
|
||||
WsState WsConsole::GetState()
|
||||
{
|
||||
WsState state = {};
|
||||
state.Model = _model;
|
||||
state.Cpu = _cpu->GetState();
|
||||
state.Ppu = _ppu->GetState();
|
||||
state.Apu = _apu->GetState();
|
||||
state.MemoryManager = _memoryManager->GetState();
|
||||
state.ControlManager = _controlManager->GetState();
|
||||
state.DmaController = _dmaController->GetState();
|
||||
state.Timer = _timer->GetState();
|
||||
state.Serial = _serial->GetState();
|
||||
state.InternalEeprom = _internalEeprom->GetState();
|
||||
state.Cart = _cart->GetState();
|
||||
if(_cartEeprom) {
|
||||
state.CartEeprom = _cartEeprom->GetState();
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
void WsConsole::GetConsoleState(BaseState& state, ConsoleType consoleType)
|
||||
{
|
||||
(WsState&)state = GetState();
|
||||
}
|
||||
|
||||
void WsConsole::Serialize(Serializer& s)
|
||||
{
|
||||
WsModel model = _model;
|
||||
SV(model);
|
||||
if(!s.IsSaving() && (model == WsModel::Monochrome) != (_model == WsModel::Monochrome)) {
|
||||
bool isMono = _model == WsModel::Monochrome;
|
||||
MessageManager::DisplayMessage("SaveStates", isMono ? "Can't load WSC state in WS mode." : "Can't load WS state in WSC mode.");
|
||||
s.SetErrorFlag();
|
||||
return;
|
||||
}
|
||||
|
||||
SV(_cpu);
|
||||
SV(_ppu);
|
||||
SV(_apu);
|
||||
|
||||
//Process cart before memory manager to ensure mappings are updated properly
|
||||
SV(_cart);
|
||||
SV(_memoryManager);
|
||||
|
||||
SV(_dmaController);
|
||||
SV(_controlManager);
|
||||
SV(_timer);
|
||||
SV(_serial);
|
||||
SV(_internalEeprom);
|
||||
|
||||
SV(_verticalMode);
|
||||
|
||||
SVArray(_workRam, _workRamSize);
|
||||
if(_saveRam && _saveRamSize) {
|
||||
SVArray(_saveRam, _saveRamSize);
|
||||
}
|
||||
if(_internalEepromData && _internalEepromSize) {
|
||||
SVArray(_internalEepromData, _internalEepromSize);
|
||||
}
|
||||
if(_cartEepromData && _cartEepromSize) {
|
||||
SVArray(_cartEepromData, _cartEepromSize);
|
||||
}
|
||||
|
||||
if(_cartEeprom) {
|
||||
SV(_cartEeprom);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
#pragma once
|
||||
#include "pch.h"
|
||||
#include "Shared/Interfaces/IConsole.h"
|
||||
#include "WS/WsTypes.h"
|
||||
|
||||
class Emulator;
|
||||
class WsCpu;
|
||||
class WsPpu;
|
||||
class WsApu;
|
||||
class WsCart;
|
||||
class WsTimer;
|
||||
class WsSerial;
|
||||
class WsMemoryManager;
|
||||
class WsControlManager;
|
||||
class WsDmaController;
|
||||
class WsEeprom;
|
||||
enum class WsModel : uint8_t;
|
||||
|
||||
class WsConsole final : public IConsole
|
||||
{
|
||||
private:
|
||||
Emulator* _emu = nullptr;
|
||||
unique_ptr<WsCpu> _cpu;
|
||||
unique_ptr<WsPpu> _ppu;
|
||||
unique_ptr<WsApu> _apu;
|
||||
unique_ptr<WsCart> _cart;
|
||||
unique_ptr<WsTimer> _timer;
|
||||
unique_ptr<WsSerial> _serial;
|
||||
unique_ptr<WsMemoryManager> _memoryManager;
|
||||
unique_ptr<WsControlManager> _controlManager;
|
||||
unique_ptr<WsDmaController> _dmaController;
|
||||
unique_ptr<WsEeprom> _internalEeprom;
|
||||
unique_ptr<WsEeprom> _cartEeprom;
|
||||
|
||||
uint8_t* _workRam = nullptr;
|
||||
uint32_t _workRamSize = 0;
|
||||
|
||||
uint8_t* _saveRam = nullptr;
|
||||
uint32_t _saveRamSize = 0;
|
||||
|
||||
uint8_t* _bootRom = nullptr;
|
||||
uint32_t _bootRomSize = 0;
|
||||
|
||||
uint8_t* _prgRom = nullptr;
|
||||
uint32_t _prgRomSize = 0;
|
||||
|
||||
uint8_t* _internalEepromData = nullptr;
|
||||
uint32_t _internalEepromSize = 0;
|
||||
|
||||
uint8_t* _cartEepromData = nullptr;
|
||||
uint32_t _cartEepromSize = 0;
|
||||
|
||||
WsModel _model = {};
|
||||
bool _verticalMode = false;
|
||||
|
||||
void InitPostBootRomState();
|
||||
|
||||
public:
|
||||
WsConsole(Emulator* emu);
|
||||
~WsConsole();
|
||||
|
||||
static vector<string> GetSupportedExtensions() { return { ".ws", ".wsc" }; }
|
||||
static vector<string> GetSupportedSignatures() { return { }; }
|
||||
|
||||
LoadRomResult LoadRom(VirtualFile& romFile) override;
|
||||
void RunFrame() override;
|
||||
|
||||
void GetScreenRotationOverride(uint32_t& rotation) override;
|
||||
bool IsColorMode();
|
||||
bool IsVerticalMode();
|
||||
WsModel GetModel();
|
||||
|
||||
void ProcessEndOfFrame();
|
||||
|
||||
void Reset() override;
|
||||
void LoadBattery();
|
||||
void SaveBattery() override;
|
||||
|
||||
BaseControlManager* GetControlManager() override;
|
||||
ConsoleRegion GetRegion() override;
|
||||
ConsoleType GetConsoleType() override;
|
||||
vector<CpuType> GetCpuTypes() override;
|
||||
uint64_t GetMasterClock() override;
|
||||
uint32_t GetMasterClockRate() override;
|
||||
double GetFps() override;
|
||||
BaseVideoFilter* GetVideoFilter(bool getDefaultFilter) override;
|
||||
PpuFrameInfo GetPpuFrame() override;
|
||||
RomFormat GetRomFormat() override;
|
||||
AudioTrackInfo GetAudioTrackInfo() override;
|
||||
void ProcessAudioPlayerAction(AudioPlayerActionParams p) override;
|
||||
AddressInfo GetAbsoluteAddress(uint32_t relAddr);
|
||||
AddressInfo GetAbsoluteAddress(AddressInfo& relAddress) override;
|
||||
AddressInfo GetRelativeAddress(AddressInfo& absAddress, CpuType cpuType) override;
|
||||
|
||||
WsState GetState();
|
||||
void GetConsoleState(BaseState& state, ConsoleType consoleType) override;
|
||||
|
||||
WsCpu* GetCpu() { return _cpu.get(); }
|
||||
WsPpu* GetPpu() { return _ppu.get(); }
|
||||
WsApu* GetApu() { return _apu.get(); }
|
||||
WsMemoryManager* GetMemoryManager() { return _memoryManager.get(); }
|
||||
|
||||
void Serialize(Serializer& s) override;
|
||||
};
|
||||
@@ -0,0 +1,117 @@
|
||||
#include "pch.h"
|
||||
#include "WS/WsConsole.h"
|
||||
#include "WS/WsControlManager.h"
|
||||
#include "WS/WsMemoryManager.h"
|
||||
#include "WS/WsController.h"
|
||||
#include "Shared/KeyManager.h"
|
||||
|
||||
WsControlManager::WsControlManager(Emulator* emu, WsConsole* console) : BaseControlManager(emu, CpuType::Ws)
|
||||
{
|
||||
_console = console;
|
||||
}
|
||||
|
||||
shared_ptr<BaseControlDevice> WsControlManager::CreateControllerDevice(ControllerType type, uint8_t port)
|
||||
{
|
||||
shared_ptr<BaseControlDevice> device;
|
||||
|
||||
WsConfig& cfg = _emu->GetSettings()->GetWsConfig();
|
||||
|
||||
switch(type) {
|
||||
default:
|
||||
case ControllerType::None: break;
|
||||
|
||||
case ControllerType::WsController:
|
||||
case ControllerType::WsControllerVertical:
|
||||
device.reset(new WsController(_emu, _console, port, cfg.ControllerHorizontal.Keys, cfg.ControllerVertical.Keys));
|
||||
break;
|
||||
}
|
||||
|
||||
return device;
|
||||
}
|
||||
|
||||
void WsControlManager::UpdateControlDevices()
|
||||
{
|
||||
WsConfig cfg = _emu->GetSettings()->GetWsConfig();
|
||||
if(_emu->GetSettings()->IsEqual(_prevConfig, cfg) && _controlDevices.size() > 0) {
|
||||
//Do nothing if configuration is unchanged
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = _deviceLock.AcquireSafe();
|
||||
|
||||
ClearDevices();
|
||||
|
||||
shared_ptr<BaseControlDevice> device(CreateControllerDevice(ControllerType::WsController, 0));
|
||||
if(device) {
|
||||
RegisterControlDevice(device);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t WsControlManager::Read()
|
||||
{
|
||||
uint8_t result = 0;
|
||||
|
||||
for(shared_ptr<BaseControlDevice>& controller : _controlDevices) {
|
||||
if(controller->GetPort() == 0 && controller->GetControllerType() == ControllerType::WsController) {
|
||||
if(_state.InputSelect & 0x10) {
|
||||
result |= controller->IsPressed(WsController::Up2) ? 0x01 : 0;
|
||||
result |= controller->IsPressed(WsController::Right2) ? 0x02 : 0;
|
||||
result |= controller->IsPressed(WsController::Down2) ? 0x04 : 0;
|
||||
result |= controller->IsPressed(WsController::Left2) ? 0x08 : 0;
|
||||
}
|
||||
if(_state.InputSelect & 0x20) {
|
||||
result |= controller->IsPressed(WsController::Up) ? 0x01 : 0;
|
||||
result |= controller->IsPressed(WsController::Right) ? 0x02 : 0;
|
||||
result |= controller->IsPressed(WsController::Down) ? 0x04 : 0;
|
||||
result |= controller->IsPressed(WsController::Left) ? 0x08 : 0;
|
||||
}
|
||||
if(_state.InputSelect & 0x40) {
|
||||
result |= controller->IsPressed(WsController::Start) ? 0x02 : 0;
|
||||
result |= controller->IsPressed(WsController::A) ? 0x04 : 0;
|
||||
result |= controller->IsPressed(WsController::B) ? 0x08 : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void WsControlManager::Write(uint8_t value)
|
||||
{
|
||||
_state.InputSelect = value & 0x70;
|
||||
}
|
||||
|
||||
bool WsControlManager::IsSoundPressed()
|
||||
{
|
||||
for(shared_ptr<BaseControlDevice>& controller : _controlDevices) {
|
||||
if(controller->GetPort() == 0 && controller->GetControllerType() == ControllerType::WsController) {
|
||||
return controller->IsPressed(WsController::Sound);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void WsControlManager::UpdateInputState()
|
||||
{
|
||||
BaseControlManager::UpdateInputState();
|
||||
|
||||
uint8_t newInput = Read();
|
||||
if((_prevInput | newInput) > _prevInput) {
|
||||
//Trigger IRQ whenever any extra bits are set compared to the previous input
|
||||
_console->GetMemoryManager()->SetIrqSource(WsIrqSource::KeyPressed);
|
||||
}
|
||||
_prevInput = newInput;
|
||||
|
||||
bool soundButtonPressed = IsSoundPressed();
|
||||
if(soundButtonPressed && !_soundButtonPressed) {
|
||||
_console->GetApu()->ChangeMasterVolume();
|
||||
}
|
||||
_soundButtonPressed = soundButtonPressed;
|
||||
}
|
||||
|
||||
void WsControlManager::Serialize(Serializer& s)
|
||||
{
|
||||
SV(_state.InputSelect);
|
||||
SV(_prevInput);
|
||||
SV(_soundButtonPressed);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
#include "pch.h"
|
||||
#include "Shared/SettingTypes.h"
|
||||
#include "Shared/BaseControlManager.h"
|
||||
#include "WS/WsTypes.h"
|
||||
|
||||
class WsConsole;
|
||||
|
||||
class WsControlManager final : public BaseControlManager
|
||||
{
|
||||
private:
|
||||
WsControlManagerState _state = {};
|
||||
WsConfig _prevConfig = {};
|
||||
|
||||
uint8_t _prevInput = 0;
|
||||
bool _soundButtonPressed = false;
|
||||
|
||||
WsConsole* _console = nullptr;
|
||||
|
||||
public:
|
||||
WsControlManager(Emulator* emu, WsConsole* console);
|
||||
|
||||
WsControlManagerState& GetState() { return _state; }
|
||||
|
||||
shared_ptr<BaseControlDevice> CreateControllerDevice(ControllerType type, uint8_t port) override;
|
||||
|
||||
void UpdateControlDevices() override;
|
||||
void UpdateInputState() override;
|
||||
|
||||
uint8_t Read();
|
||||
void Write(uint8_t value);
|
||||
|
||||
bool IsSoundPressed();
|
||||
|
||||
void Serialize(Serializer& s) override;
|
||||
};
|
||||
@@ -0,0 +1,135 @@
|
||||
#pragma once
|
||||
#include "pch.h"
|
||||
#include "WS/WsConsole.h"
|
||||
#include "Shared/BaseControlDevice.h"
|
||||
#include "Shared/Emulator.h"
|
||||
#include "Shared/EmuSettings.h"
|
||||
#include "Shared/InputHud.h"
|
||||
#include "Utilities/Serializer.h"
|
||||
|
||||
class WsController : public BaseControlDevice
|
||||
{
|
||||
private:
|
||||
WsConsole* _console = nullptr;
|
||||
vector<KeyMapping> _verticalMappings;
|
||||
uint32_t _turboSpeed = 0;
|
||||
|
||||
protected:
|
||||
string GetKeyNames() override
|
||||
{
|
||||
return "UDLRudlrSsBA";
|
||||
}
|
||||
|
||||
void InternalSetStateFromInput() override
|
||||
{
|
||||
vector<KeyMapping>& keyMappings = _console->IsVerticalMode() ? _verticalMappings : _keyMappings;
|
||||
for(KeyMapping& keyMapping : keyMappings) {
|
||||
SetPressedState(Buttons::A, keyMapping.A);
|
||||
SetPressedState(Buttons::B, keyMapping.B);
|
||||
SetPressedState(Buttons::Sound, keyMapping.GenericKey1);
|
||||
SetPressedState(Buttons::Start, keyMapping.Start);
|
||||
SetPressedState(Buttons::Up, keyMapping.Up);
|
||||
SetPressedState(Buttons::Down, keyMapping.Down);
|
||||
SetPressedState(Buttons::Left, keyMapping.Left);
|
||||
SetPressedState(Buttons::Right, keyMapping.Right);
|
||||
|
||||
SetPressedState(Buttons::Up2, keyMapping.U);
|
||||
SetPressedState(Buttons::Down2, keyMapping.D);
|
||||
SetPressedState(Buttons::Left2, keyMapping.L);
|
||||
SetPressedState(Buttons::Right2, keyMapping.R);
|
||||
|
||||
uint8_t turboFreq = 1 << (4 - _turboSpeed);
|
||||
bool turboOn = (uint8_t)(_emu->GetFrameCount() % turboFreq) < turboFreq / 2;
|
||||
if(turboOn) {
|
||||
SetPressedState(Buttons::A, keyMapping.TurboA);
|
||||
SetPressedState(Buttons::B, keyMapping.TurboB);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RefreshStateBuffer() override
|
||||
{}
|
||||
|
||||
public:
|
||||
enum Buttons { Up = 0, Down, Left, Right, Up2, Down2, Left2, Right2, Sound, Start, B, A };
|
||||
|
||||
WsController(Emulator* emu, WsConsole* console, uint8_t port, KeyMappingSet horizontalMappings, KeyMappingSet verticalMappings) : BaseControlDevice(emu, ControllerType::WsController, port, horizontalMappings)
|
||||
{
|
||||
//TODOWS turbo support
|
||||
_verticalMappings = verticalMappings.GetKeyMappingArray();
|
||||
_console = console;
|
||||
_turboSpeed = horizontalMappings.TurboSpeed;
|
||||
}
|
||||
|
||||
uint8_t ReadRam(uint16_t addr) override
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void WriteRam(uint16_t addr, uint8_t value) override
|
||||
{
|
||||
}
|
||||
|
||||
void InternalDrawController(InputHud& hud) override
|
||||
{
|
||||
if(_console->IsVerticalMode()) {
|
||||
hud.DrawOutline(28, 31);
|
||||
|
||||
hud.DrawButton(20, 20, 3, 3, IsPressed(Buttons::Right));
|
||||
hud.DrawButton(20, 26, 3, 3, IsPressed(Buttons::Left));
|
||||
hud.DrawButton(17, 23, 3, 3, IsPressed(Buttons::Up));
|
||||
hud.DrawButton(23, 23, 3, 3, IsPressed(Buttons::Down));
|
||||
|
||||
hud.DrawButton(5, 20, 3, 3, IsPressed(Buttons::Right2));
|
||||
hud.DrawButton(5, 26, 3, 3, IsPressed(Buttons::Left2));
|
||||
hud.DrawButton(2, 23, 3, 3, IsPressed(Buttons::Up2));
|
||||
hud.DrawButton(8, 23, 3, 3, IsPressed(Buttons::Down2));
|
||||
|
||||
hud.DrawButton(23, 6, 3, 3, IsPressed(Buttons::B));
|
||||
hud.DrawButton(20, 3, 3, 3, IsPressed(Buttons::A));
|
||||
|
||||
hud.DrawButton(21, 11, 2, 3, IsPressed(Buttons::Start));
|
||||
hud.DrawButton(21, 15, 2, 3, IsPressed(Buttons::Sound));
|
||||
|
||||
hud.DrawNumber(_port + 1, 13, 2);
|
||||
} else {
|
||||
hud.DrawOutline(35, 24);
|
||||
|
||||
hud.DrawButton(5, 2, 3, 3, IsPressed(Buttons::Up2));
|
||||
hud.DrawButton(5, 8, 3, 3, IsPressed(Buttons::Down2));
|
||||
hud.DrawButton(2, 5, 3, 3, IsPressed(Buttons::Left2));
|
||||
hud.DrawButton(8, 5, 3, 3, IsPressed(Buttons::Right2));
|
||||
|
||||
hud.DrawButton(5, 13, 3, 3, IsPressed(Buttons::Up));
|
||||
hud.DrawButton(5, 19, 3, 3, IsPressed(Buttons::Down));
|
||||
hud.DrawButton(2, 16, 3, 3, IsPressed(Buttons::Left));
|
||||
hud.DrawButton(8, 16, 3, 3, IsPressed(Buttons::Right));
|
||||
|
||||
hud.DrawButton(25, 19, 3, 3, IsPressed(Buttons::B));
|
||||
hud.DrawButton(29, 16, 3, 3, IsPressed(Buttons::A));
|
||||
|
||||
hud.DrawButton(14, 17, 4, 2, IsPressed(Buttons::Sound));
|
||||
hud.DrawButton(19, 17, 4, 2, IsPressed(Buttons::Start));
|
||||
|
||||
hud.DrawNumber(_port + 1, 16, 2);
|
||||
}
|
||||
}
|
||||
|
||||
vector<DeviceButtonName> GetKeyNameAssociations() override
|
||||
{
|
||||
return {
|
||||
{ "a", Buttons::A },
|
||||
{ "b", Buttons::B },
|
||||
{ "sound", Buttons::Sound },
|
||||
{ "start", Buttons::Start },
|
||||
{ "up", Buttons::Up },
|
||||
{ "down", Buttons::Down },
|
||||
{ "left", Buttons::Left },
|
||||
{ "right", Buttons::Right },
|
||||
{ "up2", Buttons::Up2 },
|
||||
{ "down2", Buttons::Down2 },
|
||||
{ "left2", Buttons::Left2 },
|
||||
{ "right2", Buttons::Right2 },
|
||||
};
|
||||
}
|
||||
};
|
||||
+2132
File diff suppressed because it is too large
Load Diff
+296
@@ -0,0 +1,296 @@
|
||||
#if (defined(DUMMYCPU) && !defined(__DUMMYWsCpu__H)) || (!defined(DUMMYCPU) && !defined(__WsCpu__H))
|
||||
#ifdef DUMMYCPU
|
||||
#define __DUMMYWsCpu__H
|
||||
#else
|
||||
#define __WsCpu__H
|
||||
#endif
|
||||
|
||||
#include "pch.h"
|
||||
#include "WS/WsCpuPrefetch.h"
|
||||
#include "WS/WsTypes.h"
|
||||
#include "Shared/MemoryOperationType.h"
|
||||
#include "Utilities/ISerializable.h"
|
||||
|
||||
class Emulator;
|
||||
class WsMemoryManager;
|
||||
|
||||
class WsCpu final : public ISerializable
|
||||
{
|
||||
private:
|
||||
struct ModRmState
|
||||
{
|
||||
uint16_t Segment;
|
||||
uint16_t Offset;
|
||||
uint8_t Mode;
|
||||
uint8_t Register;
|
||||
uint8_t Rm;
|
||||
};
|
||||
|
||||
enum class WsRepMode : uint8_t
|
||||
{
|
||||
None,
|
||||
Zero,
|
||||
NotZero
|
||||
};
|
||||
|
||||
enum class AluOp : uint8_t
|
||||
{
|
||||
Add = 0,
|
||||
Or,
|
||||
Adc,
|
||||
Sbb,
|
||||
And,
|
||||
Sub,
|
||||
Xor,
|
||||
Cmp
|
||||
};
|
||||
|
||||
enum class Grp2Mode
|
||||
{
|
||||
One,
|
||||
CL,
|
||||
Immediate
|
||||
};
|
||||
|
||||
struct PrefixState
|
||||
{
|
||||
uint16_t PrefixCount;
|
||||
WsSegment Segment;
|
||||
WsRepMode Rep;
|
||||
bool Lock;
|
||||
bool Preserve;
|
||||
};
|
||||
|
||||
static WsCpuParityTable _parity;
|
||||
|
||||
Emulator* _emu = nullptr;
|
||||
WsMemoryManager* _memoryManager = nullptr;
|
||||
|
||||
WsCpuState _state = {};
|
||||
ModRmState _modRm = {};
|
||||
PrefixState _prefix = {};
|
||||
|
||||
#ifndef DUMMYCPU
|
||||
WsCpuPrefetch _prefetch;
|
||||
#endif
|
||||
|
||||
uint16_t* _modRegLut8[4] = { &_state.AX, &_state.CX, &_state.DX, &_state.BX };
|
||||
uint16_t* _modSegLut16[4] = { &_state.ES, &_state.CS, &_state.SS, &_state.DS };
|
||||
uint16_t* _modRegLut16[8] = {
|
||||
&_state.AX, &_state.CX, &_state.DX, &_state.BX,
|
||||
&_state.SP, &_state.BP, &_state.SI, &_state.DI
|
||||
};
|
||||
|
||||
//Used to re-fill prefetch buffer with last opcode when REP prefix is used
|
||||
uint8_t _opCode = 0;
|
||||
|
||||
//Divisions/AAM set carry/overflow to the last carry/overflow produced by the previous MUL operation
|
||||
bool _mulOverflow = false;
|
||||
|
||||
template<typename T> __forceinline T ReadPort(uint16_t port);
|
||||
template<typename T> __forceinline void WritePort(uint16_t port, T value);
|
||||
|
||||
__forceinline void ProcessMemoryAccess(uint32_t addr);
|
||||
__forceinline void ProcessPortAccess(uint16_t port);
|
||||
|
||||
__forceinline uint8_t ReadCodeByte(bool forOpCode = false);
|
||||
__forceinline uint16_t ReadCodeWord();
|
||||
template<typename T> __forceinline T ReadImmediate();
|
||||
|
||||
template<typename T> __forceinline T ReadMemory(uint16_t seg, uint16_t offset);
|
||||
template<typename T> __forceinline void WriteMemory(uint16_t seg, uint16_t offset, T value);
|
||||
|
||||
template<uint8_t cycles = 1> __forceinline void Idle();
|
||||
|
||||
template<typename T> constexpr uint32_t GetMaxValue();
|
||||
template<typename T> constexpr uint32_t GetBitCount();
|
||||
template<typename T> constexpr uint32_t GetSign();
|
||||
|
||||
void Move(uint16_t& dst, uint16_t src);
|
||||
void MoveLo(uint16_t& dst, uint8_t src);
|
||||
void MoveHi(uint16_t& dst, uint8_t src);
|
||||
|
||||
void Push(uint16_t value);
|
||||
void PushSP();
|
||||
void Pop(uint16_t& dst);
|
||||
void PushSegment(uint16_t value);
|
||||
void PopSegment(uint16_t& dst);
|
||||
void PushFlags();
|
||||
void PopFlags();
|
||||
void PopMemory();
|
||||
void PopAll();
|
||||
void PushAll();
|
||||
|
||||
uint16_t GetSegment(WsSegment defaultSegment);
|
||||
|
||||
__forceinline void ReadModRmByte();
|
||||
|
||||
template<typename T> T GetModRegister(uint8_t reg) = delete;
|
||||
template<typename T> void SetModRegister(uint8_t reg, T value) = delete;
|
||||
|
||||
uint16_t GetModSegRegister(uint8_t reg);
|
||||
void SetModSegRegister(uint8_t reg, uint16_t value);
|
||||
|
||||
template<typename T> T GetModRm();
|
||||
template<typename T> void SetModRm(T value);
|
||||
|
||||
template<bool sign, typename T> void Grp1ModRm();
|
||||
template<typename T, Grp2Mode mode> void Grp2ModRm();
|
||||
template<typename T> void Grp3ModRm();
|
||||
template<typename T> void Grp45ModRm();
|
||||
|
||||
template<typename T> void TestModRm();
|
||||
template<typename T> void TestImmediate();
|
||||
|
||||
template<typename T> void ExchangeModRm();
|
||||
void Exchange(uint16_t& x, uint16_t& y);
|
||||
|
||||
template<bool direction, typename T> void MoveModRm();
|
||||
template<bool direction> void MoveSegment();
|
||||
template<bool direction, typename T> void MoveAccumulator();
|
||||
template<typename T> void MoveImmediate();
|
||||
|
||||
template<AluOp op, bool direction, typename T> void ProcessAluModRm();
|
||||
template<AluOp op, typename T> void ProcessAluImm();
|
||||
template<typename T> T GetAluResult(AluOp op, T param1, T param2);
|
||||
|
||||
template<typename T> void Inc(T& dst);
|
||||
template<typename T> void Dec(T& dst);
|
||||
|
||||
template<typename T> T Add(T x, T y, uint8_t carry);
|
||||
template<typename T> T Sub(T x, T y, uint8_t borrow);
|
||||
|
||||
template<typename T> void MultSignedModRm();
|
||||
template<typename T> void MulSigned(T x, T y);
|
||||
template<typename T> int32_t GetMultiplyResult(T x, T y);
|
||||
template<typename T> void MulUnsigned(T x, T y);
|
||||
|
||||
template<typename T> void DivSigned(T y);
|
||||
template<typename T> void DivUnsigned(T y);
|
||||
void ProcessInvalidDiv();
|
||||
|
||||
template<typename T> void UpdateFlags(T result);
|
||||
template<typename T> T And(T x, T y);
|
||||
template<typename T> T Or(T x, T y);
|
||||
template<typename T> T Xor(T x, T y);
|
||||
template<typename T> T ROL(T x, uint8_t shift);
|
||||
template<typename T> T ROR(T x, uint8_t shift);
|
||||
template<typename T> T RCL(T x, uint8_t shift);
|
||||
template<typename T> T RCR(T x, uint8_t shift);
|
||||
template<typename T> T SHL(T x, uint8_t shift);
|
||||
template<typename T> T SHR(T x, uint8_t shift);
|
||||
template<typename T> T SAR(T x, uint8_t shift);
|
||||
|
||||
void JumpFar();
|
||||
void Jump(bool condition);
|
||||
void JumpNearWord();
|
||||
void Call(uint16_t offset);
|
||||
void CallNearWord();
|
||||
void CallFar();
|
||||
void CallFar(uint16_t segment, uint16_t offset);
|
||||
void Loop();
|
||||
void LoopIf(bool condition);
|
||||
void Ret();
|
||||
void RetImm();
|
||||
void RetFar();
|
||||
void RetFarImm();
|
||||
void RetInterrupt();
|
||||
|
||||
void Interrupt(uint8_t vector, bool pushFirstPrefix = false);
|
||||
void InterruptOverflow();
|
||||
|
||||
void Enter();
|
||||
void Leave();
|
||||
|
||||
void NOP();
|
||||
void FP01();
|
||||
|
||||
void BOUND();
|
||||
|
||||
void SALC();
|
||||
|
||||
void CBW();
|
||||
void CWD();
|
||||
|
||||
void LAHF();
|
||||
void SAHF();
|
||||
|
||||
void LdsLesLeaModRm();
|
||||
|
||||
uint16_t LoadSegment();
|
||||
void LDS();
|
||||
void LES();
|
||||
void LEA();
|
||||
|
||||
void XLAT();
|
||||
|
||||
void AdjustAscii(bool forSub);
|
||||
void AAA();
|
||||
void AAS();
|
||||
|
||||
void AAD();
|
||||
void AAM();
|
||||
|
||||
void AdjustDecimal(bool forSub);
|
||||
void DAA();
|
||||
void DAS();
|
||||
|
||||
template<typename T, uint8_t delay> void Out(uint16_t port, T data);
|
||||
template<typename T, bool isDxPort> void InStoreAx(uint16_t port);
|
||||
|
||||
template<typename T> void INS();
|
||||
template<typename T> void OUTS();
|
||||
|
||||
template<typename T> void ProcessStringOperation(bool incSi, bool incDi);
|
||||
template<typename T> void ProcessStringCmpOperation(bool incSi);
|
||||
template<typename T> void MOVS();
|
||||
template<typename T> void STOS();
|
||||
template<typename T> void LODS();
|
||||
template<typename T> void CMPS();
|
||||
template<typename T> void SCAS();
|
||||
|
||||
void Undefined();
|
||||
void Wait();
|
||||
void Halt();
|
||||
|
||||
void SetFlagValue(bool& flag, bool value);
|
||||
|
||||
public:
|
||||
WsCpu(Emulator* emu, WsMemoryManager* memoryManager);
|
||||
|
||||
WsCpuState& GetState() { return _state; }
|
||||
uint64_t GetCycleCount() { return _state.CycleCount; }
|
||||
|
||||
void ProcessCpuCycle();
|
||||
|
||||
__forceinline void IncCycleCount() { _state.CycleCount++; }
|
||||
void ClearPrefetch();
|
||||
|
||||
uint32_t GetProgramCounter(bool adjustForRepLoop = false);
|
||||
|
||||
void Exec();
|
||||
void ExecOpCode();
|
||||
|
||||
void Serialize(Serializer& s) override;
|
||||
|
||||
#ifdef DUMMYCPU
|
||||
private:
|
||||
uint32_t _memOpCounter = 0;
|
||||
MemoryOperationInfo _memOperations[32] = {};
|
||||
bool _memWordAccess[32] = {};
|
||||
|
||||
public:
|
||||
void SetDummyState(WsCpuState& state);
|
||||
uint32_t GetOperationCount();
|
||||
void LogMemoryOperation(uint32_t addr, uint16_t value, MemoryOperationType type, MemoryType memType, bool isWordAccess);
|
||||
MemoryOperationInfo GetOperationInfo(uint32_t index);
|
||||
bool IsWordAccess(uint32_t index);
|
||||
#endif
|
||||
};
|
||||
|
||||
template<> uint8_t WsCpu::GetModRegister<uint8_t>(uint8_t reg);
|
||||
template<> uint16_t WsCpu::GetModRegister<uint16_t>(uint8_t reg);
|
||||
template<> void WsCpu::SetModRegister<uint8_t>(uint8_t reg, uint8_t value);
|
||||
template<> void WsCpu::SetModRegister<uint16_t>(uint8_t reg, uint16_t value);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,93 @@
|
||||
#include "pch.h"
|
||||
#include "WS/WsCpuPrefetch.h"
|
||||
#include "WS/WsMemoryManager.h"
|
||||
#include "WS/WsCpu.h"
|
||||
|
||||
WsCpuPrefetch::WsCpuPrefetch(WsCpu* cpu, WsMemoryManager* memoryManager)
|
||||
{
|
||||
_cpu = cpu;
|
||||
_memoryManager = memoryManager;
|
||||
}
|
||||
|
||||
bool WsCpuPrefetch::IsFull()
|
||||
{
|
||||
return _size >= 16;
|
||||
}
|
||||
|
||||
void WsCpuPrefetch::PushByte(uint8_t value)
|
||||
{
|
||||
_data[(_writePos++) & 0x0F] = value;
|
||||
_fetchIp++;
|
||||
_size++;
|
||||
}
|
||||
|
||||
void WsCpuPrefetch::Prefetch()
|
||||
{
|
||||
_cpu->ProcessCpuCycle();
|
||||
|
||||
_waitCycles++;
|
||||
|
||||
if(IsFull()) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t addr = ((_fetchCs << 4) + _fetchIp) & 0xFFFFF;
|
||||
uint8_t cycles = _memoryManager->GetWaitStates(addr);
|
||||
if(_waitCycles < cycles) {
|
||||
return;
|
||||
}
|
||||
|
||||
_waitCycles = 0;
|
||||
|
||||
bool isWordBus = _memoryManager->IsWordBus(addr);
|
||||
|
||||
//TODOWS debugger can't see these reads
|
||||
PushByte(_memoryManager->InternalRead(addr));
|
||||
if(!IsFull() && isWordBus && (_fetchIp & 0x01)) {
|
||||
PushByte(_memoryManager->InternalRead(((_fetchCs << 4) + _fetchIp)) & 0xFFFFF);
|
||||
}
|
||||
}
|
||||
|
||||
void WsCpuPrefetch::ProcessRep(uint8_t opCode)
|
||||
{
|
||||
if(IsFull()) {
|
||||
_writePos--;
|
||||
_fetchIp--;
|
||||
_size--;
|
||||
}
|
||||
|
||||
_readPos--;
|
||||
_size++;
|
||||
_data[_readPos & 0x0F] = opCode;
|
||||
}
|
||||
|
||||
uint8_t WsCpuPrefetch::Read()
|
||||
{
|
||||
while(_size < 2) {
|
||||
Prefetch();
|
||||
}
|
||||
|
||||
_size--;
|
||||
return _data[(_readPos++) & 0x0F];
|
||||
}
|
||||
|
||||
void WsCpuPrefetch::Clear(uint16_t cs, uint16_t ip)
|
||||
{
|
||||
_fetchCs = cs;
|
||||
_fetchIp = ip;
|
||||
_size = 0;
|
||||
_readPos = 0;
|
||||
_writePos = 0;
|
||||
_waitCycles = 0;
|
||||
}
|
||||
|
||||
void WsCpuPrefetch::Serialize(Serializer& s)
|
||||
{
|
||||
SV(_fetchCs);
|
||||
SV(_fetchIp);
|
||||
SV(_readPos);
|
||||
SV(_writePos);
|
||||
SV(_size);
|
||||
SV(_waitCycles);
|
||||
SVArray(_data, 16);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
#include "pch.h"
|
||||
#include "Utilities/ISerializable.h"
|
||||
|
||||
class WsCpu;
|
||||
class WsMemoryManager;
|
||||
|
||||
class WsCpuPrefetch final : public ISerializable
|
||||
{
|
||||
private:
|
||||
uint16_t _fetchCs = 0;
|
||||
uint16_t _fetchIp = 0;
|
||||
|
||||
uint8_t _readPos = 0;
|
||||
uint8_t _writePos = 0;
|
||||
uint8_t _size = 0;
|
||||
|
||||
uint8_t _waitCycles = 0;
|
||||
|
||||
uint8_t _data[16] = {};
|
||||
|
||||
WsMemoryManager* _memoryManager = nullptr;
|
||||
WsCpu* _cpu = nullptr;
|
||||
|
||||
bool IsFull();
|
||||
void PushByte(uint8_t value);
|
||||
|
||||
public:
|
||||
WsCpuPrefetch(WsCpu* cpu, WsMemoryManager* memoryManager);
|
||||
|
||||
void Prefetch();
|
||||
void ProcessRep(uint8_t opCode);
|
||||
uint8_t Read();
|
||||
void Clear(uint16_t cs, uint16_t ip);
|
||||
|
||||
void Serialize(Serializer& s) override;
|
||||
};
|
||||
@@ -0,0 +1,115 @@
|
||||
#include "pch.h"
|
||||
#include "WS/WsDefaultVideoFilter.h"
|
||||
#include "WS/WsConsole.h"
|
||||
#include "Shared/EmuSettings.h"
|
||||
#include "Shared/Emulator.h"
|
||||
#include "Shared/ColorUtilities.h"
|
||||
#include "Shared/RewindManager.h"
|
||||
|
||||
WsDefaultVideoFilter::WsDefaultVideoFilter(Emulator* emu, WsConsole* console, bool applyNtscFilter) : BaseVideoFilter(emu), _ntscFilter(emu)
|
||||
{
|
||||
_emu = emu;
|
||||
_console = console;
|
||||
_applyNtscFilter = applyNtscFilter;
|
||||
_prevFrame = new uint16_t[WsConstants::MaxPixelCount];
|
||||
InitLookupTable();
|
||||
}
|
||||
|
||||
WsDefaultVideoFilter::~WsDefaultVideoFilter()
|
||||
{
|
||||
delete[] _prevFrame;
|
||||
}
|
||||
|
||||
uint32_t WsDefaultVideoFilter::BlendPixels(uint32_t a, uint32_t b)
|
||||
{
|
||||
return ((((a) ^ (b)) & 0xfffefefeL) >> 1) + ((a) & (b));
|
||||
}
|
||||
|
||||
void WsDefaultVideoFilter::InitLookupTable()
|
||||
{
|
||||
VideoConfig config = _emu->GetSettings()->GetVideoConfig();
|
||||
|
||||
InitConversionMatrix(config.Hue, config.Saturation);
|
||||
|
||||
for(int rgb444 = 0; rgb444 < 0x1000; rgb444++) {
|
||||
uint8_t r = (rgb444 >> 8) & 0xF;
|
||||
uint8_t g = (rgb444 >> 4) & 0xF;
|
||||
uint8_t b = rgb444 & 0xF;
|
||||
|
||||
if(_adjustColors) {
|
||||
uint8_t r2 = r * 13 + g * 2 + b;
|
||||
uint8_t g2 = g * 12 + b * 4;
|
||||
uint8_t b2 = r * 3 + g * 2 + b * 11;
|
||||
r = r2;
|
||||
g = g2;
|
||||
b = b2;
|
||||
} else {
|
||||
r = ColorUtilities::Convert4BitTo8Bit(r);
|
||||
g = ColorUtilities::Convert4BitTo8Bit(g);
|
||||
b = ColorUtilities::Convert4BitTo8Bit(b);
|
||||
}
|
||||
|
||||
if(config.Hue != 0 || config.Saturation != 0 || config.Brightness != 0 || config.Contrast != 0) {
|
||||
ApplyColorOptions(r, g, b, config.Brightness, config.Contrast);
|
||||
_calculatedPalette[rgb444] = 0xFF000000 | (r << 16) | (g << 8) | b;
|
||||
} else {
|
||||
_calculatedPalette[rgb444] = 0xFF000000 | (r << 16) | (g << 8) | b;
|
||||
}
|
||||
}
|
||||
|
||||
_videoConfig = config;
|
||||
}
|
||||
|
||||
FrameInfo WsDefaultVideoFilter::GetFrameInfo()
|
||||
{
|
||||
if(_applyNtscFilter) {
|
||||
FrameInfo frameInfo;
|
||||
frameInfo.Width = SNES_NTSC_OUT_WIDTH(_baseFrameInfo.Width);
|
||||
frameInfo.Height = _baseFrameInfo.Height;
|
||||
return frameInfo;
|
||||
}
|
||||
return BaseVideoFilter::GetFrameInfo();
|
||||
}
|
||||
|
||||
void WsDefaultVideoFilter::OnBeforeApplyFilter()
|
||||
{
|
||||
VideoConfig config = _emu->GetSettings()->GetVideoConfig();
|
||||
WsConfig wsConfig = _emu->GetSettings()->GetWsConfig();
|
||||
|
||||
bool adjustColors = wsConfig.LcdAdjustColors;
|
||||
if(_videoConfig.Hue != config.Hue || _videoConfig.Saturation != config.Saturation || _videoConfig.Contrast != config.Contrast || _videoConfig.Brightness != config.Brightness || _adjustColors != adjustColors) {
|
||||
_adjustColors = adjustColors;
|
||||
InitLookupTable();
|
||||
}
|
||||
bool blendFrames = wsConfig.BlendFrames && !_emu->GetRewindManager()->IsRewinding() && !_emu->IsPaused();
|
||||
if(_blendFrames != blendFrames) {
|
||||
_blendFrames = blendFrames;
|
||||
memset(_prevFrame, 0, WsConstants::MaxPixelCount * sizeof(uint16_t));
|
||||
}
|
||||
_videoConfig = config;
|
||||
}
|
||||
|
||||
void WsDefaultVideoFilter::ApplyFilter(uint16_t* ppuOutputBuffer)
|
||||
{
|
||||
uint32_t* out = GetOutputBuffer();
|
||||
FrameInfo size = _baseFrameInfo;
|
||||
|
||||
if(_blendFrames && _prevFrameSize.Width == size.Width && _prevFrameSize.Height == size.Height) {
|
||||
for(uint32_t i = 0, len = size.Height * size.Width; i < len; i++) {
|
||||
out[i] = BlendPixels(_calculatedPalette[_prevFrame[i]], _calculatedPalette[ppuOutputBuffer[i]]);
|
||||
}
|
||||
} else {
|
||||
for(uint32_t i = 0, len = size.Height * size.Width; i < len; i++) {
|
||||
out[i] = _calculatedPalette[ppuOutputBuffer[i]];
|
||||
}
|
||||
}
|
||||
|
||||
if(_blendFrames) {
|
||||
std::copy(ppuOutputBuffer, ppuOutputBuffer + WsConstants::MaxPixelCount, _prevFrame);
|
||||
_prevFrameSize = size;
|
||||
}
|
||||
|
||||
if(_applyNtscFilter) {
|
||||
_ntscFilter.ApplyFilter(out, size.Width, size.Height, 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
#include "pch.h"
|
||||
#include "Shared/Video/BaseVideoFilter.h"
|
||||
#include "Shared/Video/GenericNtscFilter.h"
|
||||
|
||||
class WsConsole;
|
||||
class Emulator;
|
||||
|
||||
class WsDefaultVideoFilter final : public BaseVideoFilter
|
||||
{
|
||||
private:
|
||||
Emulator* _emu = nullptr;
|
||||
WsConsole* _console = nullptr;
|
||||
uint32_t _calculatedPalette[0x1000] = {};
|
||||
VideoConfig _videoConfig = {};
|
||||
|
||||
FrameInfo _prevFrameSize = {};
|
||||
uint16_t* _prevFrame = nullptr;
|
||||
bool _blendFrames = false;
|
||||
bool _adjustColors = false;
|
||||
|
||||
bool _applyNtscFilter = false;
|
||||
GenericNtscFilter _ntscFilter;
|
||||
|
||||
uint32_t BlendPixels(uint32_t a, uint32_t b);
|
||||
|
||||
void InitLookupTable();
|
||||
|
||||
protected:
|
||||
FrameInfo GetFrameInfo() override;
|
||||
|
||||
void OnBeforeApplyFilter() override;
|
||||
|
||||
public:
|
||||
WsDefaultVideoFilter(Emulator* emu, WsConsole* console, bool applyNtscFilter);
|
||||
~WsDefaultVideoFilter();
|
||||
|
||||
void ApplyFilter(uint16_t* ppuOutputBuffer) override;
|
||||
};
|
||||
@@ -0,0 +1,213 @@
|
||||
#include "pch.h"
|
||||
#include "WS/WsDmaController.h"
|
||||
#include "WS/WsMemoryManager.h"
|
||||
#include "WS/APU/WsApu.h"
|
||||
#include "Utilities/BitUtilities.h"
|
||||
#include "Utilities/Serializer.h"
|
||||
|
||||
void WsDmaController::Init(WsMemoryManager* memoryManager, WsApu* apu)
|
||||
{
|
||||
_memoryManager = memoryManager;
|
||||
_apu = apu;
|
||||
}
|
||||
|
||||
void WsDmaController::RunGeneralDma()
|
||||
{
|
||||
int offset = (_state.GdmaControl & 0x40) ? -2 : 2;
|
||||
|
||||
if(_state.GdmaLength == 0 || _memoryManager->GetWaitStates(_state.GdmaSrc) > 1 || !_memoryManager->IsWordBus(_state.GdmaSrc)) {
|
||||
//When length is 0, or if transfer has an invalid source address, stop processing immediately
|
||||
//The 5 cycles of setup time aren't executed in this scenario
|
||||
return;
|
||||
}
|
||||
|
||||
//GDMA takes 5+2*length cycles
|
||||
_memoryManager->Exec();
|
||||
_memoryManager->Exec();
|
||||
_memoryManager->Exec();
|
||||
_memoryManager->Exec();
|
||||
_memoryManager->Exec();
|
||||
|
||||
while(_state.GdmaLength) {
|
||||
if(_memoryManager->GetWaitStates(_state.GdmaSrc) > 1 || !_memoryManager->IsWordBus(_state.GdmaSrc)) {
|
||||
//ROM with wait states and 8-bit buses are not allowed (SRAM, cart when 16-bit flag isn't set)
|
||||
break;
|
||||
}
|
||||
|
||||
uint16_t value = _memoryManager->Read<uint16_t>((_state.GdmaSrc >> 4) & 0xF000, (uint16_t)_state.GdmaSrc, MemoryOperationType::DmaRead);
|
||||
_memoryManager->Write<uint16_t>(0, _state.GdmaDest, value, MemoryOperationType::DmaWrite);
|
||||
|
||||
_state.GdmaSrc = (_state.GdmaSrc + offset) & 0xFFFFF;
|
||||
_state.GdmaDest = _state.GdmaDest + offset;
|
||||
_state.GdmaLength -= 2;
|
||||
}
|
||||
|
||||
_state.GdmaControl &= ~0x80;
|
||||
}
|
||||
|
||||
void WsDmaController::ProcessSoundDma()
|
||||
{
|
||||
if(!_state.SdmaEnabled || _state.SdmaLength == 0) {
|
||||
//TODOWS what does enabling when length = 0 do?
|
||||
return;
|
||||
}
|
||||
|
||||
if(_state.SdmaTimer == 0) {
|
||||
_state.SdmaTimer = _state.SdmaFrequency;
|
||||
|
||||
//Sound DMA steals 6 cycles + N access cycles to run
|
||||
//4 cycles at the start + N read cycles + 1 dma write cycle
|
||||
_memoryManager->Exec();
|
||||
_memoryManager->Exec();
|
||||
_memoryManager->Exec();
|
||||
_memoryManager->Exec();
|
||||
_memoryManager->Exec();
|
||||
|
||||
uint8_t sampleValue = _memoryManager->Read<uint8_t>((_state.SdmaSrc >> 4) & 0xF000, (uint16_t)_state.SdmaSrc, MemoryOperationType::DmaRead);
|
||||
|
||||
if(_state.SdmaHold) {
|
||||
sampleValue = 0;
|
||||
}
|
||||
|
||||
if(!_state.SdmaHold) {
|
||||
int offset = _state.SdmaDecrement ? -1 : 1;
|
||||
_state.SdmaSrc = (_state.SdmaSrc + offset) & 0xFFFFF;
|
||||
_state.SdmaLength--;
|
||||
|
||||
if(_state.SdmaLength == 0) {
|
||||
if(_state.SdmaRepeat) {
|
||||
_state.SdmaSrc = _state.SdmaSrcReloadValue;
|
||||
_state.SdmaLength = _state.SdmaLengthReloadValue;
|
||||
} else {
|
||||
_state.SdmaEnabled = false;
|
||||
_state.SdmaControl &= 0x7F;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sampleValue = 0;
|
||||
}
|
||||
|
||||
_memoryManager->Exec();
|
||||
_apu->WriteDma(_state.SdmaHyperVoice, sampleValue);
|
||||
} else {
|
||||
_state.SdmaTimer--;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t WsDmaController::ReadPort(uint16_t port)
|
||||
{
|
||||
switch(port) {
|
||||
case 0x40: return BitUtilities::GetBits<0>(_state.GdmaSrc);
|
||||
case 0x41: return BitUtilities::GetBits<8>(_state.GdmaSrc);
|
||||
case 0x42: return BitUtilities::GetBits<16>(_state.GdmaSrc);
|
||||
case 0x43: return 0;
|
||||
case 0x44: return BitUtilities::GetBits<0>(_state.GdmaDest);
|
||||
case 0x45: return BitUtilities::GetBits<8>(_state.GdmaDest);
|
||||
case 0x46: return BitUtilities::GetBits<0>(_state.GdmaLength);
|
||||
case 0x47: return BitUtilities::GetBits<8>(_state.GdmaLength);
|
||||
case 0x48: return _state.GdmaControl;
|
||||
|
||||
case 0x4A: return BitUtilities::GetBits<0>(_state.SdmaSrc);
|
||||
case 0x4B: return BitUtilities::GetBits<8>(_state.SdmaSrc);
|
||||
case 0x4C: return BitUtilities::GetBits<16>(_state.SdmaSrc);
|
||||
case 0x4D: return 0;
|
||||
case 0x4E: return BitUtilities::GetBits<0>(_state.SdmaLength);
|
||||
case 0x4F: return BitUtilities::GetBits<8>(_state.SdmaLength);
|
||||
case 0x50: return BitUtilities::GetBits<16>(_state.SdmaLength);
|
||||
case 0x51: return 0;
|
||||
|
||||
case 0x52: return _state.SdmaControl;
|
||||
case 0x53: return 0;
|
||||
}
|
||||
|
||||
//TODOWS openbus
|
||||
return 0x90;
|
||||
}
|
||||
|
||||
void WsDmaController::WritePort(uint16_t port, uint8_t value)
|
||||
{
|
||||
switch(port) {
|
||||
case 0x40: BitUtilities::SetBits<0>(_state.GdmaSrc, value & 0xFE); break;
|
||||
case 0x41: BitUtilities::SetBits<8>(_state.GdmaSrc, value); break;
|
||||
case 0x42: BitUtilities::SetBits<16>(_state.GdmaSrc, value & 0x0F); break;
|
||||
case 0x43: break;
|
||||
case 0x44: BitUtilities::SetBits<0>(_state.GdmaDest, value & 0xFE); break;
|
||||
case 0x45: BitUtilities::SetBits<8>(_state.GdmaDest, value); break;
|
||||
case 0x46: BitUtilities::SetBits<0>(_state.GdmaLength, value & 0xFE); break;
|
||||
case 0x47: BitUtilities::SetBits<8>(_state.GdmaLength, value); break;
|
||||
case 0x48:
|
||||
_state.GdmaControl = value & 0xC0;
|
||||
if(_state.GdmaControl & 0x80) {
|
||||
RunGeneralDma();
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x4A:
|
||||
BitUtilities::SetBits<0>(_state.SdmaSrc, value);
|
||||
BitUtilities::SetBits<0>(_state.SdmaSrcReloadValue, value);
|
||||
break;
|
||||
|
||||
case 0x4B:
|
||||
BitUtilities::SetBits<8>(_state.SdmaSrc, value);
|
||||
BitUtilities::SetBits<8>(_state.SdmaSrcReloadValue, value);
|
||||
break;
|
||||
|
||||
case 0x4C:
|
||||
BitUtilities::SetBits<16>(_state.SdmaSrc, value & 0x0F);
|
||||
BitUtilities::SetBits<16>(_state.SdmaSrcReloadValue, value & 0x0F);
|
||||
break;
|
||||
|
||||
case 0x4E:
|
||||
BitUtilities::SetBits<0>(_state.SdmaLength, value);
|
||||
BitUtilities::SetBits<0>(_state.SdmaLengthReloadValue, value);
|
||||
break;
|
||||
|
||||
case 0x4F:
|
||||
BitUtilities::SetBits<8>(_state.SdmaLength, value);
|
||||
BitUtilities::SetBits<8>(_state.SdmaLengthReloadValue, value);
|
||||
break;
|
||||
|
||||
case 0x50:
|
||||
BitUtilities::SetBits<16>(_state.SdmaLength, value & 0x0F);
|
||||
BitUtilities::SetBits<16>(_state.SdmaLengthReloadValue, value & 0x0F);
|
||||
break;
|
||||
|
||||
case 0x52:
|
||||
_state.SdmaControl = value & 0xDF;
|
||||
|
||||
switch(value & 0x03) {
|
||||
default:
|
||||
case 0: _state.SdmaFrequency = 5; break;
|
||||
case 1: _state.SdmaFrequency = 3; break;
|
||||
case 2: _state.SdmaFrequency = 1; break;
|
||||
case 3: _state.SdmaFrequency = 0; break;
|
||||
}
|
||||
|
||||
_state.SdmaHold = value & 0x04;
|
||||
_state.SdmaRepeat = value & 0x08;
|
||||
_state.SdmaHyperVoice = value & 0x10;
|
||||
_state.SdmaDecrement = value & 0x40;
|
||||
_state.SdmaEnabled = value & 0x80;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void WsDmaController::Serialize(Serializer& s)
|
||||
{
|
||||
SV(_state.GdmaSrc);
|
||||
SV(_state.SdmaSrc);
|
||||
SV(_state.SdmaLength);
|
||||
SV(_state.SdmaSrcReloadValue);
|
||||
SV(_state.SdmaLengthReloadValue);
|
||||
SV(_state.GdmaDest);
|
||||
SV(_state.GdmaLength);
|
||||
SV(_state.GdmaControl);
|
||||
SV(_state.SdmaControl);
|
||||
SV(_state.SdmaEnabled);
|
||||
SV(_state.SdmaDecrement);
|
||||
SV(_state.SdmaHyperVoice);
|
||||
SV(_state.SdmaRepeat);
|
||||
SV(_state.SdmaHold);
|
||||
SV(_state.SdmaFrequency);
|
||||
SV(_state.SdmaTimer);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
#include "pch.h"
|
||||
#include "WS/WsTypes.h"
|
||||
#include "Utilities/ISerializable.h"
|
||||
|
||||
class WsMemoryManager;
|
||||
class WsApu;
|
||||
|
||||
class WsDmaController final : public ISerializable
|
||||
{
|
||||
private:
|
||||
WsMemoryManager* _memoryManager = nullptr;
|
||||
WsDmaControllerState _state = {};
|
||||
WsApu* _apu = nullptr;
|
||||
|
||||
public:
|
||||
void Init(WsMemoryManager* memoryManager, WsApu* apu);
|
||||
|
||||
void RunGeneralDma();
|
||||
void ProcessSoundDma();
|
||||
|
||||
WsDmaControllerState& GetState() { return _state; }
|
||||
|
||||
uint8_t ReadPort(uint16_t port);
|
||||
void WritePort(uint16_t port, uint8_t value);
|
||||
|
||||
void Serialize(Serializer& s) override;
|
||||
};
|
||||
@@ -0,0 +1,225 @@
|
||||
#include "pch.h"
|
||||
#include "WS/WsEeprom.h"
|
||||
#include "WS/WsConsole.h"
|
||||
#include "Shared/Emulator.h"
|
||||
#include "Shared/BatteryManager.h"
|
||||
#include "Utilities/BitUtilities.h"
|
||||
#include "Utilities/Serializer.h"
|
||||
|
||||
WsEeprom::WsEeprom(Emulator* emu, WsConsole* console, WsEepromSize size, uint8_t* eepromData, bool isInternal)
|
||||
{
|
||||
_emu = emu;
|
||||
_console = console;
|
||||
_isInternal = isInternal;
|
||||
_data = eepromData;
|
||||
_state.Size = size;
|
||||
_state.Idle = true;
|
||||
_state.ReadDone = true;
|
||||
_state.WriteDisabled = true;
|
||||
|
||||
if(_isInternal) {
|
||||
InitInternalEepromData();
|
||||
}
|
||||
}
|
||||
|
||||
WsEepromCommand WsEeprom::GetCommand()
|
||||
{
|
||||
uint16_t command = _state.Command;
|
||||
if(GetSize() != WsEepromSize::Size128) {
|
||||
command >>= 4;
|
||||
}
|
||||
|
||||
switch(command & 0xFFC0) {
|
||||
case 0x0180: return WsEepromCommand::Read;
|
||||
case 0x0140: return WsEepromCommand::Write;
|
||||
case 0x01C0: return WsEepromCommand::Erase;
|
||||
case 0x0100:
|
||||
switch((command >> 4) & 0x03) {
|
||||
case 0x00: return WsEepromCommand::WriteDisable;
|
||||
case 0x01: return WsEepromCommand::WriteAll;
|
||||
case 0x02: return WsEepromCommand::EraseAll;
|
||||
case 0x03: return WsEepromCommand::WriteEnable;
|
||||
}
|
||||
}
|
||||
|
||||
return WsEepromCommand::Unknown;
|
||||
}
|
||||
|
||||
WsEepromSize WsEeprom::GetSize()
|
||||
{
|
||||
if(_isInternal && _state.Size == WsEepromSize::Size2kb && !_console->IsColorMode()) {
|
||||
//Process commands as is the eeprom was the monochrome model's 128-byte eeprom when color mode is disabled
|
||||
return WsEepromSize::Size128;
|
||||
}
|
||||
return _state.Size;
|
||||
}
|
||||
|
||||
uint16_t WsEeprom::GetCommandAddress()
|
||||
{
|
||||
switch(GetSize()) {
|
||||
case WsEepromSize::Size128: return _state.Command & 0x3F;
|
||||
case WsEepromSize::Size1kb: return _state.Command & 0x1FF;
|
||||
case WsEepromSize::Size2kb: return _state.Command & 0x3FF;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void WsEeprom::WriteValue(uint16_t addr, uint16_t value)
|
||||
{
|
||||
if(!_state.WriteDisabled && (!_state.InternalEepromWriteProtected || addr < 0x30)) {
|
||||
if(_isInternal) {
|
||||
_emu->ProcessMemoryAccess<CpuType::Ws, MemoryType::WsInternalEeprom, MemoryOperationType::Write>(addr << 1, value);
|
||||
} else {
|
||||
_emu->ProcessMemoryAccess<CpuType::Ws, MemoryType::WsCartEeprom, MemoryOperationType::Write>(addr << 1, value);
|
||||
}
|
||||
_data[addr << 1] = (uint8_t)value;
|
||||
_data[(addr << 1) + 1] = (value >> 8);
|
||||
}
|
||||
}
|
||||
|
||||
string WsEeprom::ConvertToEepromString(string in)
|
||||
{
|
||||
for(size_t i = 0; i < in.size(); i++) {
|
||||
in[i] = in[i] - 0x36;
|
||||
}
|
||||
return in;
|
||||
}
|
||||
|
||||
void WsEeprom::InitInternalEepromData()
|
||||
{
|
||||
memset(_data, 0, (uint32_t)_state.Size);
|
||||
|
||||
string name;
|
||||
switch(_console->GetModel()) {
|
||||
case WsModel::Monochrome:
|
||||
name = "WONDERSWAN";
|
||||
break;
|
||||
|
||||
case WsModel::Color:
|
||||
case WsModel::SwanCrystal:
|
||||
//Set volume to max by default
|
||||
_data[0x83] = 0x03;
|
||||
|
||||
if(_console->GetModel() == WsModel::Color) {
|
||||
name = "WONDERSWANCOLOR";
|
||||
} else {
|
||||
name = "SWANCRYSTAL";
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
memcpy(_data + 0x60, ConvertToEepromString(name).c_str(), name.size());
|
||||
}
|
||||
|
||||
void WsEeprom::WritePort(uint8_t port, uint8_t value)
|
||||
{
|
||||
switch(port) {
|
||||
case 0x00: BitUtilities::SetBits<0>(_state.WriteBuffer, value); break;
|
||||
case 0x01: BitUtilities::SetBits<8>(_state.WriteBuffer, value); break;
|
||||
case 0x02: BitUtilities::SetBits<0>(_state.Command, value); break;
|
||||
case 0x03: BitUtilities::SetBits<8>(_state.Command, value); break;
|
||||
|
||||
case 0x04:
|
||||
bool read = value & 0x10;
|
||||
bool write = value & 0x20;
|
||||
bool other = value & 0x40;
|
||||
bool writeProtect = value & 0x80;
|
||||
if((uint8_t)read + (uint8_t)write + (uint8_t)other + (uint8_t)writeProtect > 1) {
|
||||
//Invalid operation, do nothing
|
||||
return;
|
||||
}
|
||||
|
||||
//TODOWS abort (cart eeprom)
|
||||
if(writeProtect && _isInternal) {
|
||||
_state.InternalEepromWriteProtected = true;
|
||||
return;
|
||||
}
|
||||
|
||||
WsEepromCommand cmd = GetCommand();
|
||||
uint16_t addr = GetCommandAddress();
|
||||
|
||||
if(read) {
|
||||
if(cmd == WsEepromCommand::Read) {
|
||||
_state.ReadBuffer = _data[addr << 1] | (_data[(addr << 1) + 1] << 8);
|
||||
if(_isInternal) {
|
||||
_emu->ProcessMemoryAccess<CpuType::Ws, MemoryType::WsInternalEeprom, MemoryOperationType::Read>(addr << 1, _state.ReadBuffer);
|
||||
} else {
|
||||
_emu->ProcessMemoryAccess<CpuType::Ws, MemoryType::WsCartEeprom, MemoryOperationType::Read>(addr << 1, _state.ReadBuffer);
|
||||
}
|
||||
_state.ReadDone = true;
|
||||
}
|
||||
} else if(write) {
|
||||
if(cmd == WsEepromCommand::Write) {
|
||||
WriteValue(addr, _state.WriteBuffer);
|
||||
} else if(cmd == WsEepromCommand::WriteAll) {
|
||||
for(int i = 0; i < (int)_state.Size; i += 2) {
|
||||
WriteValue(i >> 1, _state.WriteBuffer);
|
||||
}
|
||||
}
|
||||
} else if(other) {
|
||||
switch(cmd) {
|
||||
case WsEepromCommand::Erase:
|
||||
WriteValue(addr, 0xFFFF);
|
||||
break;
|
||||
|
||||
case WsEepromCommand::WriteDisable:
|
||||
_state.WriteDisabled = true;
|
||||
break;
|
||||
|
||||
case WsEepromCommand::EraseAll:
|
||||
for(int i = 0; i < (int)_state.Size; i += 2) {
|
||||
WriteValue(i >> 1, 0xFFFF);
|
||||
}
|
||||
break;
|
||||
|
||||
case WsEepromCommand::WriteEnable:
|
||||
_state.WriteDisabled = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t WsEeprom::ReadPort(uint8_t port)
|
||||
{
|
||||
switch(port) {
|
||||
case 0x00: return BitUtilities::GetBits<0>(_state.ReadBuffer);
|
||||
case 0x01: return BitUtilities::GetBits<8>(_state.ReadBuffer);
|
||||
case 0x02: return BitUtilities::GetBits<0>(_state.Command);
|
||||
case 0x03: return BitUtilities::GetBits<8>(_state.Command);
|
||||
|
||||
case 0x04:
|
||||
return (
|
||||
(_state.ReadDone ? 0x01 : 0) |
|
||||
(_state.Idle ? 0x02 : 0) |
|
||||
(_state.InternalEepromWriteProtected ? 0x80 : 0)
|
||||
);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void WsEeprom::LoadBattery()
|
||||
{
|
||||
_emu->GetBatteryManager()->LoadBattery(_isInternal ? ".ieeprom" : ".eeprom", _data, (uint32_t)_state.Size);
|
||||
}
|
||||
|
||||
void WsEeprom::SaveBattery()
|
||||
{
|
||||
_emu->GetBatteryManager()->SaveBattery(_isInternal ? ".ieeprom" : ".eeprom", _data, (uint32_t)_state.Size);
|
||||
}
|
||||
|
||||
void WsEeprom::Serialize(Serializer& s)
|
||||
{
|
||||
SV(_state.Size);
|
||||
SV(_state.ReadBuffer);
|
||||
SV(_state.WriteBuffer);
|
||||
SV(_state.Command);
|
||||
SV(_state.Control);
|
||||
SV(_state.WriteDisabled);
|
||||
SV(_state.ReadDone);
|
||||
SV(_state.Idle);
|
||||
SV(_state.InternalEepromWriteProtected);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
#include "pch.h"
|
||||
#include "WS/WsTypes.h"
|
||||
#include "Utilities/ISerializable.h"
|
||||
|
||||
class WsConsole;
|
||||
class Emulator;
|
||||
|
||||
enum class WsEepromCommand
|
||||
{
|
||||
Read,
|
||||
Write,
|
||||
Erase,
|
||||
WriteDisable,
|
||||
WriteEnable,
|
||||
EraseAll,
|
||||
WriteAll,
|
||||
Unknown
|
||||
};
|
||||
|
||||
class WsEeprom final : public ISerializable
|
||||
{
|
||||
private:
|
||||
WsEepromState _state = {};
|
||||
WsConsole* _console = nullptr;
|
||||
Emulator* _emu = nullptr;
|
||||
uint8_t* _data = nullptr;
|
||||
bool _isInternal = false;
|
||||
|
||||
WsEepromCommand GetCommand();
|
||||
WsEepromSize GetSize();
|
||||
uint16_t GetCommandAddress();
|
||||
|
||||
void WriteValue(uint16_t addr, uint16_t value);
|
||||
|
||||
string ConvertToEepromString(string in);
|
||||
void InitInternalEepromData();
|
||||
|
||||
public:
|
||||
WsEeprom(Emulator* emu, WsConsole* console, WsEepromSize size, uint8_t* eepromData, bool isInternal);
|
||||
|
||||
WsEepromState& GetState() { return _state; }
|
||||
|
||||
void WritePort(uint8_t port, uint8_t value);
|
||||
uint8_t ReadPort(uint8_t port);
|
||||
|
||||
void LoadBattery();
|
||||
void SaveBattery();
|
||||
|
||||
void Serialize(Serializer& s);
|
||||
};
|
||||
@@ -0,0 +1,480 @@
|
||||
#include "pch.h"
|
||||
#include "WS/WsMemoryManager.h"
|
||||
#include "WS/WsConsole.h"
|
||||
#include "WS/WsDmaController.h"
|
||||
#include "WS/WsControlManager.h"
|
||||
#include "WS/WsTimer.h"
|
||||
#include "WS/WsSerial.h"
|
||||
#include "WS/WsEeprom.h"
|
||||
#include "WS/Carts/WsCart.h"
|
||||
#include "Shared/MessageManager.h"
|
||||
#include "Shared/Emulator.h"
|
||||
#include "Utilities/Serializer.h"
|
||||
|
||||
void WsMemoryManager::Init(Emulator* emu, WsConsole* console, WsCpu* cpu, WsPpu* ppu, WsControlManager* controlManager, WsCart* cart, WsTimer* timer, WsDmaController* dmaController, WsEeprom* eeprom, WsApu* apu, WsSerial* serial)
|
||||
{
|
||||
_emu = emu;
|
||||
_console = console;
|
||||
_cpu = cpu;
|
||||
_cart = cart;
|
||||
_ppu = ppu;
|
||||
_apu = apu;
|
||||
_timer = timer;
|
||||
_dmaController = dmaController;
|
||||
_controlManager = controlManager;
|
||||
_eeprom = eeprom;
|
||||
_serial = serial;
|
||||
|
||||
_workRamSize = _emu->GetMemory(MemoryType::WsWorkRam).Size;
|
||||
|
||||
_prgRom = (uint8_t*)_emu->GetMemory(MemoryType::WsPrgRom).Memory;
|
||||
_prgRomSize = _emu->GetMemory(MemoryType::WsPrgRom).Size;
|
||||
|
||||
_saveRam = (uint8_t*)_emu->GetMemory(MemoryType::WsCartRam).Memory;
|
||||
_saveRamSize = _emu->GetMemory(MemoryType::WsCartRam).Size;
|
||||
|
||||
_bootRom = (uint8_t*)_emu->GetMemory(MemoryType::WsBootRom).Memory;
|
||||
_bootRomSize = _emu->GetMemory(MemoryType::WsBootRom).Size;
|
||||
|
||||
RefreshMappings();
|
||||
}
|
||||
|
||||
void WsMemoryManager::RefreshMappings()
|
||||
{
|
||||
_cart->RefreshMappings();
|
||||
if(_state.ColorEnabled) {
|
||||
Map(0, 0xFFFF, MemoryType::WsWorkRam, 0, false);
|
||||
} else {
|
||||
Unmap(0x4000, 0xFFFF);
|
||||
Map(0, 0x3FFF, MemoryType::WsWorkRam, 0, false);
|
||||
}
|
||||
|
||||
if(!_state.BootRomDisabled) {
|
||||
Map(0x100000 - _bootRomSize, 0xFFFFF, MemoryType::WsBootRom, 0, true);
|
||||
}
|
||||
}
|
||||
|
||||
void WsMemoryManager::Map(uint32_t start, uint32_t end, MemoryType type, uint32_t offset, bool readonly)
|
||||
{
|
||||
uint8_t* src = (uint8_t*)_emu->GetMemory(type).Memory;
|
||||
uint32_t size = _emu->GetMemory(type).Size;
|
||||
|
||||
if(size > 0) {
|
||||
while(offset >= size) {
|
||||
offset -= size;
|
||||
}
|
||||
|
||||
src += offset;
|
||||
for(uint32_t i = start; i < end; i += 0x1000) {
|
||||
_reads[i >> 12] = src;
|
||||
_writes[i >> 12] = readonly ? nullptr : src;
|
||||
|
||||
if(src) {
|
||||
src += 0x1000;
|
||||
offset = (offset + 0x1000);
|
||||
if(offset >= size) {
|
||||
offset = 0;
|
||||
src = (uint8_t*)_emu->GetMemory(type).Memory;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Unmap(start, end);
|
||||
}
|
||||
}
|
||||
|
||||
void WsMemoryManager::Unmap(uint32_t start, uint32_t end)
|
||||
{
|
||||
for(uint32_t i = start; i < end; i += 0x1000) {
|
||||
_reads[i >> 12] = nullptr;
|
||||
_writes[i >> 12] = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t WsMemoryManager::DebugRead(uint32_t addr)
|
||||
{
|
||||
uint8_t* handler = _reads[addr >> 12];
|
||||
if(handler) {
|
||||
return handler[addr & 0xFFF];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void WsMemoryManager::DebugWrite(uint32_t addr, uint8_t value)
|
||||
{
|
||||
uint8_t* handler = _writes[addr >> 12];
|
||||
if(handler) {
|
||||
handler[addr & 0xFFF] = value;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T WsMemoryManager::DebugCpuRead(uint16_t seg, uint16_t offset)
|
||||
{
|
||||
uint32_t addr = (seg << 4) + offset;
|
||||
if constexpr(std::is_same<T, uint16_t>::value) {
|
||||
bool splitReads = !IsWordBus(addr) || (addr & 0x01);
|
||||
if(splitReads) {
|
||||
uint8_t lo = DebugCpuRead<uint8_t>(seg, offset);
|
||||
uint8_t hi = DebugCpuRead<uint8_t>(seg, offset + 1);
|
||||
return lo | (hi << 8);
|
||||
} else {
|
||||
uint8_t lo = DebugRead(addr);
|
||||
addr = (seg << 4) + (uint16_t)(offset + 1);
|
||||
uint8_t hi = DebugRead(addr);
|
||||
return lo | (hi << 8);
|
||||
}
|
||||
} else {
|
||||
return DebugRead(addr);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T WsMemoryManager::ReadPort(uint16_t port)
|
||||
{
|
||||
if constexpr(std::is_same<T, uint16_t>::value) {
|
||||
bool splitReads = !IsWordPort(port) || (port & 0x01);
|
||||
if(splitReads) {
|
||||
uint8_t lo = ReadPort<uint8_t>(port);
|
||||
uint8_t hi = ReadPort<uint8_t>(port + 1);
|
||||
return lo | (hi << 8);
|
||||
} else {
|
||||
Exec();
|
||||
|
||||
uint16_t value;
|
||||
if(IsUnmappedPort(port)) {
|
||||
//TODOWS open bus
|
||||
value = 0x9090;
|
||||
} else {
|
||||
uint8_t lo = InternalReadPort(port, true);
|
||||
uint8_t hi = InternalReadPort(port + 1, true);
|
||||
value = lo | (hi << 8);
|
||||
}
|
||||
|
||||
_emu->ProcessMemoryAccess<CpuType::Ws, MemoryType::WsPort, MemoryOperationType::Read>(port, value);
|
||||
return value;
|
||||
}
|
||||
} else {
|
||||
Exec();
|
||||
|
||||
//TODOWS open bus
|
||||
uint8_t value = IsUnmappedPort(port) ? 0x90 : InternalReadPort(port, false);
|
||||
_emu->ProcessMemoryAccess<CpuType::Ws, MemoryType::WsPort, MemoryOperationType::Read>(port, value);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void WsMemoryManager::WritePort(uint16_t port, T value)
|
||||
{
|
||||
if constexpr(std::is_same<T, uint16_t>::value) {
|
||||
bool splitWrites = !IsWordPort(port) || (port & 0x01);
|
||||
if(splitWrites) {
|
||||
WritePort<uint8_t>(port, value);
|
||||
WritePort<uint8_t>(port + 1, value >> 8);
|
||||
} else {
|
||||
Exec();
|
||||
_emu->ProcessMemoryAccess<CpuType::Ws, MemoryType::WsPort, MemoryOperationType::Write>(port, value);
|
||||
|
||||
if(!IsUnmappedPort(port)) {
|
||||
InternalWritePort(port, value, true);
|
||||
InternalWritePort(port + 1, value >> 8, true);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Exec();
|
||||
_emu->ProcessMemoryAccess<CpuType::Ws, MemoryType::WsPort, MemoryOperationType::Write>(port, value);
|
||||
|
||||
if(!IsUnmappedPort(port)) {
|
||||
InternalWritePort(port, value, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T WsMemoryManager::DebugReadPort(uint16_t port)
|
||||
{
|
||||
if constexpr(std::is_same<T, uint16_t>::value) {
|
||||
uint8_t lo = DebugReadPort<uint8_t>(port);
|
||||
uint8_t hi = DebugReadPort<uint8_t>(port + 1);
|
||||
return lo | (hi << 8);
|
||||
} else {
|
||||
if(IsUnmappedPort(port)) {
|
||||
//TODOWS open bus
|
||||
return 0x90;
|
||||
}
|
||||
|
||||
if(port == 0xB1 || port == 0xB3 || port == 0xB5) {
|
||||
//TODOWS implement peek to avoid side-effects for these ports
|
||||
return 0;
|
||||
}
|
||||
|
||||
return InternalReadPort(port, false);
|
||||
}
|
||||
}
|
||||
|
||||
bool WsMemoryManager::IsUnmappedPort(uint16_t port)
|
||||
{
|
||||
return (port & 0x100) || (port >= 0x100 && (port & 0xFF) >= 0xB7);
|
||||
}
|
||||
|
||||
uint8_t WsMemoryManager::InternalReadPort(uint16_t port, bool isWordAccess)
|
||||
{
|
||||
port &= 0xFF;
|
||||
|
||||
if(port <= 0x3F) {
|
||||
return _ppu->ReadPort(port);
|
||||
} else if(port >= 0x40 && port <= 0x53 && _state.ColorEnabled) {
|
||||
return _dmaController->ReadPort(port);
|
||||
} else if(port >= 0x64 && port <= 0x6B && _state.ColorEnabled) {
|
||||
return _apu->Read(port); //HyperVoice
|
||||
} else if(port >= 0x70 && port <= 0x77 && _console->GetModel() == WsModel::SwanCrystal) {
|
||||
return _ppu->ReadLcdConfigPort(port);
|
||||
} else if(port >= 0x80 && port <= 0x9F) {
|
||||
return _apu->Read(port);
|
||||
} else if(port >= 0xA2 && port <= 0xAB) {
|
||||
return _timer->ReadPort(port);
|
||||
} else if(port >= 0xBA && port <= 0xBF) {
|
||||
if(_console->GetModel() != WsModel::Monochrome || isWordAccess || !(port & 0x01)) {
|
||||
return _eeprom->ReadPort(port - 0xBA);
|
||||
} else {
|
||||
//TODOWS open bus
|
||||
return 0x90;
|
||||
}
|
||||
} else if(port >= 0xC0) {
|
||||
return _cart->ReadPort(port);
|
||||
} else {
|
||||
switch(port) {
|
||||
case 0x60: return _state.SystemControl2;
|
||||
case 0x62: return _console->GetModel() == WsModel::SwanCrystal ? 0x80 : 0;
|
||||
case 0xA0:
|
||||
return (
|
||||
(_state.BootRomDisabled ? 0x01 : 0) |
|
||||
(_console->GetModel() == WsModel::Monochrome ? 0 : 0x02) |
|
||||
(_state.CartWordBus ? 0x04 : 0) |
|
||||
(_state.SlowRom ? 0x08 : 0) |
|
||||
0x80 //mbc authentication?
|
||||
);
|
||||
|
||||
case 0xB0: return GetIrqVector();
|
||||
case 0xB1: return _serial->Read(port);
|
||||
case 0xB2: return _state.EnabledIrqs;
|
||||
case 0xB3: return _serial->Read(port);
|
||||
case 0xB4: return GetActiveIrqs();
|
||||
case 0xB5: return _controlManager->Read();
|
||||
case 0xB7: return _state.EnableLowBatteryNmi ? 0x10 : 0;
|
||||
default:
|
||||
//TODOWS open bus
|
||||
LogDebug("[Debug] Read - missing handler: $" + HexUtilities::ToHex(port));
|
||||
return 0x90;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WsMemoryManager::InternalWritePort(uint16_t port, uint8_t value, bool isWordAccess)
|
||||
{
|
||||
port &= 0xFF;
|
||||
|
||||
if(port <= 0x3F) {
|
||||
_ppu->WritePort(port, value);
|
||||
} else if(port >= 0x40 && port <= 0x53 && _state.ColorEnabled) {
|
||||
return _dmaController->WritePort(port, value);
|
||||
} else if(port >= 0x64 && port <= 0x6B && _state.ColorEnabled) {
|
||||
_apu->Write(port, value); //HyperVoice
|
||||
} else if(port >= 0x70 && port <= 0x77 && !_state.BootRomDisabled && _console->GetModel() == WsModel::SwanCrystal) {
|
||||
_ppu->WriteLcdConfigPort(port, value);
|
||||
} else if(port >= 0x80 && port <= 0x9F) {
|
||||
_apu->Write(port, value);
|
||||
} else if(port >= 0xA2 && port <= 0xAB) {
|
||||
_timer->WritePort(port, value);
|
||||
} else if(port >= 0xBA && port <= 0xBF) {
|
||||
if(_console->GetModel() != WsModel::Monochrome || isWordAccess || !(port & 0x01)) {
|
||||
_eeprom->WritePort(port - 0xBA, value);
|
||||
}
|
||||
} else if(port >= 0xC0) {
|
||||
_cart->WritePort(port, value);
|
||||
} else {
|
||||
switch(port) {
|
||||
case 0x60:
|
||||
if(_console->GetModel() != WsModel::Monochrome) {
|
||||
_state.SystemControl2 = value & 0xEF;
|
||||
_state.ColorEnabled = value & 0x80;
|
||||
_state.Enable4bpp = value & 0x40;
|
||||
_state.Enable4bppPacked = value & 0x20;
|
||||
_state.SlowSram = value & 0x02;
|
||||
_state.SlowPort = value & 0x08;
|
||||
RefreshMappings();
|
||||
WsVideoMode mode = WsVideoMode::Monochrome;
|
||||
if(_state.ColorEnabled) {
|
||||
mode = WsVideoMode::Color2bpp;
|
||||
if(_state.Enable4bpp) {
|
||||
mode = WsVideoMode::Color4bpp;
|
||||
if(_state.Enable4bppPacked) {
|
||||
mode = WsVideoMode::Color4bppPacked;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ppu->SetVideoMode(mode);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x62:
|
||||
if(value & 0x01) {
|
||||
MessageManager::DisplayMessage("WS", "Power off.");
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xA0:
|
||||
_state.BootRomDisabled = (value & 0x01) || _state.BootRomDisabled;
|
||||
_state.CartWordBus = value & 0x04;
|
||||
_state.SlowRom = value & 0x08;
|
||||
RefreshMappings();
|
||||
break;
|
||||
|
||||
case 0xB0: _state.IrqVectorOffset = value & 0xF8; break;
|
||||
case 0xB1: _serial->Write(port, value); break;
|
||||
case 0xB2: _state.EnabledIrqs = value; break;
|
||||
case 0xB3: _serial->Write(port, value); break;
|
||||
case 0xB5: _controlManager->Write(value); break;
|
||||
case 0xB6: _state.ActiveIrqs &= ~value; break;
|
||||
case 0xB7: _state.EnableLowBatteryNmi = value & 0x10; break;
|
||||
|
||||
default:
|
||||
LogDebug("[Debug] Write - missing handler: $" + HexUtilities::ToHex(port) + " = " + HexUtilities::ToHex(value));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool WsMemoryManager::IsWordBus(uint32_t addr)
|
||||
{
|
||||
switch(addr >> 16) {
|
||||
case 0: return true;
|
||||
case 1: return false;
|
||||
default: return _state.CartWordBus;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t WsMemoryManager::GetWaitStates(uint32_t addr)
|
||||
{
|
||||
switch(addr >> 16) {
|
||||
case 0: return 1;
|
||||
case 1: return 1 + (uint8_t)_state.SlowSram;
|
||||
default: return 1 + (uint8_t)_state.SlowRom;
|
||||
}
|
||||
}
|
||||
|
||||
bool WsMemoryManager::IsWordPort(uint16_t port)
|
||||
{
|
||||
return port < 0xC0;
|
||||
}
|
||||
|
||||
uint8_t WsMemoryManager::GetPortWaitStates(uint16_t port)
|
||||
{
|
||||
return 1 + (uint8_t)(_state.SlowPort && port >= 0xC0 && port <= 0xFF);
|
||||
}
|
||||
|
||||
void WsMemoryManager::SetIrqSource(WsIrqSource src)
|
||||
{
|
||||
if(_state.EnabledIrqs & (uint8_t)src) {
|
||||
_state.ActiveIrqs |= (uint8_t)src;
|
||||
}
|
||||
}
|
||||
|
||||
void WsMemoryManager::ClearIrqSource(WsIrqSource src)
|
||||
{
|
||||
_state.ActiveIrqs &= (uint8_t)src;
|
||||
}
|
||||
|
||||
uint8_t WsMemoryManager::GetActiveIrqs()
|
||||
{
|
||||
if(_serial->HasSendIrq() && (_state.EnabledIrqs & (uint8_t)WsIrqSource::UartSendReady)) {
|
||||
_state.ActiveIrqs |= (uint8_t)WsIrqSource::UartSendReady;
|
||||
}
|
||||
return _state.ActiveIrqs;
|
||||
}
|
||||
|
||||
uint8_t WsMemoryManager::GetIrqVector()
|
||||
{
|
||||
uint8_t activeIrqs = GetActiveIrqs();
|
||||
for(int i = 7; i >= 0; i--) {
|
||||
if(activeIrqs & (1 << i)) {
|
||||
return _state.IrqVectorOffset + i;
|
||||
}
|
||||
}
|
||||
return _state.IrqVectorOffset;
|
||||
}
|
||||
|
||||
AddressInfo WsMemoryManager::GetAbsoluteAddress(uint32_t relAddr)
|
||||
{
|
||||
if(relAddr < _workRamSize) {
|
||||
return { (int)(relAddr & (_workRamSize - 1)), MemoryType::WsWorkRam };
|
||||
}
|
||||
|
||||
uint8_t* ptr = _reads[relAddr >> 12];
|
||||
if(ptr >= _prgRom && ptr < _prgRom + _prgRomSize) {
|
||||
return { (int)(ptr - _prgRom + (relAddr & 0xFFF)), MemoryType::WsPrgRom };
|
||||
} else if(ptr >= _saveRam && ptr < _saveRam + _saveRamSize) {
|
||||
return { (int)(ptr - _saveRam + (relAddr & 0xFFF)), MemoryType::WsCartRam };
|
||||
} else if(ptr >= _bootRom && ptr < _bootRom + _bootRomSize) {
|
||||
return { (int)(ptr - _bootRom + (relAddr & 0xFFF)), MemoryType::WsBootRom };
|
||||
}
|
||||
|
||||
return { -1, MemoryType::None };
|
||||
}
|
||||
|
||||
int WsMemoryManager::GetRelativeAddress(AddressInfo& absAddress)
|
||||
{
|
||||
switch(absAddress.Type) {
|
||||
case MemoryType::WsPrgRom:
|
||||
case MemoryType::WsCartRam:
|
||||
case MemoryType::WsBootRom: {
|
||||
//Use the closest mirror to the current code segment
|
||||
uint8_t startBank = (_cpu->GetState().CS >> 12) << 4;
|
||||
for(int32_t i = 0; i < 256; i++) {
|
||||
uint8_t bank = (uint8_t)(startBank + i);
|
||||
AddressInfo blockAddr = GetAbsoluteAddress(bank * 0x1000);
|
||||
if(blockAddr.Type == absAddress.Type && (blockAddr.Address & ~0xFFF) == (absAddress.Address & ~0xFFF)) {
|
||||
return (bank << 12) | (absAddress.Address & 0xFFF);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
case MemoryType::WsWorkRam:
|
||||
return absAddress.Address & (_workRamSize - 1);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void WsMemoryManager::Serialize(Serializer& s)
|
||||
{
|
||||
SV(_state.ActiveIrqs);
|
||||
SV(_state.EnabledIrqs);
|
||||
SV(_state.IrqVectorOffset);
|
||||
SV(_state.SystemControl2);
|
||||
SV(_state.ColorEnabled);
|
||||
SV(_state.Enable4bpp);
|
||||
SV(_state.Enable4bppPacked);
|
||||
SV(_state.BootRomDisabled);
|
||||
SV(_state.SlowPort);
|
||||
SV(_state.SlowRom);
|
||||
SV(_state.SlowSram);
|
||||
SV(_state.CartWordBus);
|
||||
SV(_state.EnableLowBatteryNmi);
|
||||
|
||||
if(!s.IsSaving()) {
|
||||
RefreshMappings();
|
||||
}
|
||||
}
|
||||
|
||||
template uint8_t WsMemoryManager::ReadPort(uint16_t port);
|
||||
template uint16_t WsMemoryManager::ReadPort(uint16_t port);
|
||||
template uint8_t WsMemoryManager::DebugReadPort(uint16_t port);
|
||||
template uint16_t WsMemoryManager::DebugReadPort(uint16_t port);
|
||||
|
||||
template void WsMemoryManager::WritePort(uint16_t port, uint8_t value);
|
||||
template void WsMemoryManager::WritePort(uint16_t port, uint16_t value);
|
||||
|
||||
template uint8_t WsMemoryManager::DebugCpuRead(uint16_t seg, uint16_t offset);
|
||||
template uint16_t WsMemoryManager::DebugCpuRead(uint16_t seg, uint16_t offset);
|
||||
@@ -0,0 +1,167 @@
|
||||
#pragma once
|
||||
#include "pch.h"
|
||||
#include "WS/WsCpu.h"
|
||||
#include "WS/APU/WsApu.h"
|
||||
#include "WS/WsPpu.h"
|
||||
#include "WS/WsTypes.h"
|
||||
#include "Utilities/ISerializable.h"
|
||||
|
||||
class WsConsole;
|
||||
class WsTimer;
|
||||
class WsControlManager;
|
||||
class WsCart;
|
||||
class WsSerial;
|
||||
class WsDmaController;
|
||||
class WsEeprom;
|
||||
class Emulator;
|
||||
|
||||
class WsMemoryManager final : public ISerializable
|
||||
{
|
||||
private:
|
||||
WsConsole* _console = nullptr;
|
||||
WsCpu* _cpu = nullptr;
|
||||
WsPpu* _ppu = nullptr;
|
||||
WsApu* _apu = nullptr;
|
||||
WsControlManager* _controlManager = nullptr;
|
||||
WsCart* _cart = nullptr;
|
||||
WsTimer* _timer = nullptr;
|
||||
WsSerial* _serial = nullptr;
|
||||
WsDmaController* _dmaController = nullptr;
|
||||
WsEeprom* _eeprom = nullptr;
|
||||
Emulator* _emu = nullptr;
|
||||
|
||||
uint8_t* _prgRom = nullptr;
|
||||
uint32_t _prgRomSize = 0;
|
||||
uint8_t* _saveRam = nullptr;
|
||||
uint32_t _saveRamSize = 0;
|
||||
uint8_t* _bootRom = nullptr;
|
||||
uint32_t _bootRomSize = 0;
|
||||
uint32_t _workRamSize = 0;
|
||||
|
||||
WsMemoryManagerState _state = {};
|
||||
|
||||
uint8_t* _reads[256] = {};
|
||||
uint8_t* _writes[256] = {};
|
||||
|
||||
bool IsWordPort(uint16_t port);
|
||||
uint8_t GetPortWaitStates(uint16_t port);
|
||||
bool IsUnmappedPort(uint16_t port);
|
||||
|
||||
public:
|
||||
WsMemoryManager() {}
|
||||
|
||||
void Init(Emulator* emu, WsConsole* console, WsCpu* cpu, WsPpu* ppu, WsControlManager* controlManager, WsCart* cart, WsTimer* timer, WsDmaController* dmaController, WsEeprom* eeprom, WsApu* apu, WsSerial* serial);
|
||||
WsMemoryManagerState& GetState() { return _state; }
|
||||
|
||||
void RefreshMappings();
|
||||
|
||||
void Map(uint32_t start, uint32_t end, MemoryType type, uint32_t offset, bool readonly);
|
||||
void Unmap(uint32_t start, uint32_t end);
|
||||
|
||||
__forceinline void Exec()
|
||||
{
|
||||
_cpu->IncCycleCount();
|
||||
_ppu->Exec();
|
||||
_apu->Run();
|
||||
}
|
||||
|
||||
__forceinline uint8_t InternalRead(uint32_t addr)
|
||||
{
|
||||
uint8_t* handler = _reads[addr >> 12];
|
||||
uint8_t value = 0x90;
|
||||
if(handler) {
|
||||
value = handler[addr & 0xFFF];
|
||||
}
|
||||
|
||||
//TODOWS open bus
|
||||
return value;
|
||||
}
|
||||
|
||||
__forceinline void InternalWrite(uint32_t addr, uint8_t value)
|
||||
{
|
||||
//TODOWS open bus
|
||||
uint8_t* handler = _writes[addr >> 12];
|
||||
if(handler) {
|
||||
handler[addr & 0xFFF] = value;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t DebugRead(uint32_t addr);
|
||||
void DebugWrite(uint32_t addr, uint8_t value);
|
||||
|
||||
template<typename T>
|
||||
T DebugCpuRead(uint16_t seg, uint16_t offset);
|
||||
|
||||
template<typename T>
|
||||
__forceinline T Read(uint16_t seg, uint16_t offset, MemoryOperationType opType = MemoryOperationType::Read)
|
||||
{
|
||||
uint32_t addr = ((seg << 4) + offset) & 0xFFFFF;
|
||||
if constexpr(std::is_same<T, uint16_t>::value) {
|
||||
bool splitReads = !IsWordBus(addr) || (addr & 0x01);
|
||||
if(splitReads) {
|
||||
uint8_t lo = Read<uint8_t>(seg, offset);
|
||||
uint8_t hi = Read<uint8_t>(seg, offset + 1);
|
||||
return lo | (hi << 8);
|
||||
} else {
|
||||
Exec();
|
||||
uint8_t lo = InternalRead(addr);
|
||||
uint8_t hi = InternalRead(((seg << 4) + (uint16_t)(offset + 1)) & 0xFFFFF);
|
||||
uint16_t value = lo | (hi << 8);
|
||||
_emu->ProcessMemoryRead<CpuType::Ws, 2>(addr, value, opType);
|
||||
return value;
|
||||
}
|
||||
} else {
|
||||
Exec();
|
||||
uint8_t value = InternalRead(addr);
|
||||
_emu->ProcessMemoryRead<CpuType::Ws, 1>(addr, value, opType);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
__forceinline void Write(uint16_t seg, uint16_t offset, T value, MemoryOperationType opType = MemoryOperationType::Write)
|
||||
{
|
||||
uint32_t addr = ((seg << 4) + offset) & 0xFFFFF;
|
||||
if constexpr(std::is_same<T, uint16_t>::value) {
|
||||
bool splitWrites = !IsWordBus(addr) || (addr & 0x01);
|
||||
if(splitWrites) {
|
||||
Write<uint8_t>(seg, offset, (uint8_t)value);
|
||||
Write<uint8_t>(seg, offset + 1, (uint8_t)(value >> 8));
|
||||
} else {
|
||||
Exec();
|
||||
_emu->ProcessMemoryWrite<CpuType::Ws, 2>(addr, value, opType);
|
||||
InternalWrite(addr, (uint8_t)value);
|
||||
InternalWrite(((seg << 4) + (uint16_t)(offset + 1)) & 0xFFFFF, value >> 8);
|
||||
}
|
||||
} else {
|
||||
Exec();
|
||||
_emu->ProcessMemoryWrite<CpuType::Ws>(addr, value, opType);
|
||||
InternalWrite(addr, value);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T> T ReadPort(uint16_t port);
|
||||
template<typename T> void WritePort(uint16_t port, T value);
|
||||
uint8_t InternalReadPort(uint16_t port, bool isWordAccess);
|
||||
void InternalWritePort(uint16_t port, uint8_t value, bool isWordAccess);
|
||||
|
||||
template<typename T> T DebugReadPort(uint16_t port);
|
||||
|
||||
bool IsWordBus(uint32_t addr);
|
||||
uint8_t GetWaitStates(uint32_t addr);
|
||||
|
||||
void SetIrqSource(WsIrqSource src);
|
||||
void ClearIrqSource(WsIrqSource src);
|
||||
uint8_t GetActiveIrqs();
|
||||
uint8_t GetIrqVector();
|
||||
|
||||
__forceinline bool HasPendingIrq()
|
||||
{
|
||||
return (GetActiveIrqs() & _state.EnabledIrqs) != 0;
|
||||
}
|
||||
|
||||
AddressInfo GetAbsoluteAddress(uint32_t relAddr);
|
||||
int GetRelativeAddress(AddressInfo& absAddress);
|
||||
|
||||
void Serialize(Serializer& s) override;
|
||||
};
|
||||
@@ -0,0 +1,746 @@
|
||||
#include "pch.h"
|
||||
#include "WS/WsConsole.h"
|
||||
#include "WS/WsConsole.h"
|
||||
#include "WS/WsTimer.h"
|
||||
#include "WS/WsControlManager.h"
|
||||
#include "WS/WsMemoryManager.h"
|
||||
#include "WS/APU/WsApu.h"
|
||||
#include "Shared/EmuSettings.h"
|
||||
#include "Shared/NotificationManager.h"
|
||||
#include "Shared/RewindManager.h"
|
||||
#include "Shared/Video/VideoDecoder.h"
|
||||
#include "Shared/RenderedFrame.h"
|
||||
#include "Shared/EventType.h"
|
||||
#include "Shared/MessageManager.h"
|
||||
#include "Utilities/HexUtilities.h"
|
||||
#include "Utilities/Serializer.h"
|
||||
|
||||
WsPpu::WsPpu(Emulator* emu, WsConsole* console, WsTimer* timer, uint8_t* vram)
|
||||
{
|
||||
_emu = emu;
|
||||
_console = console;
|
||||
_timer = timer;
|
||||
_vram = vram;
|
||||
|
||||
_state.LastScanline = 158;
|
||||
_state.BackPorchScanline = 155;
|
||||
_state.ShowVolumeIconFrame = UINT32_MAX;
|
||||
|
||||
_screenWidth = WsConstants::ScreenWidth;
|
||||
_screenHeight = WsConstants::ScreenHeight;
|
||||
if(console->GetModel() == WsModel::Monochrome) {
|
||||
_screenHeight += 13;
|
||||
} else {
|
||||
_screenWidth += 13;
|
||||
}
|
||||
|
||||
_outputBuffers[0] = new uint16_t[WsConstants::MaxPixelCount];
|
||||
_outputBuffers[1] = new uint16_t[WsConstants::MaxPixelCount];
|
||||
memset(_outputBuffers[0], 0, WsConstants::MaxPixelCount * sizeof(uint16_t));
|
||||
memset(_outputBuffers[1], 0, WsConstants::MaxPixelCount * sizeof(uint16_t));
|
||||
_currentBuffer = _outputBuffers[0];
|
||||
|
||||
_showIcons = _emu->GetSettings()->GetWsConfig().LcdShowIcons;
|
||||
}
|
||||
|
||||
WsPpu::~WsPpu()
|
||||
{
|
||||
delete[] _outputBuffers[0];
|
||||
delete[] _outputBuffers[1];
|
||||
}
|
||||
|
||||
void WsPpu::SetVideoMode(WsVideoMode mode)
|
||||
{
|
||||
_state.NextMode = mode;
|
||||
}
|
||||
|
||||
void WsPpu::ProcessHblank()
|
||||
{
|
||||
_timer->TickHorizontalTimer();
|
||||
if(_state.Scanline < WsConstants::ScreenHeight) {
|
||||
switch(_state.Mode) {
|
||||
case WsVideoMode::Monochrome: DrawScanline<WsVideoMode::Monochrome>(); break;
|
||||
case WsVideoMode::Color2bpp: DrawScanline<WsVideoMode::Color2bpp>(); break;
|
||||
case WsVideoMode::Color4bpp: DrawScanline<WsVideoMode::Color4bpp>(); break;
|
||||
case WsVideoMode::Color4bppPacked: DrawScanline<WsVideoMode::Color4bppPacked>(); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WsPpu::ProcessEndOfScanline()
|
||||
{
|
||||
_state.Cycle = 0;
|
||||
_state.Scanline++;
|
||||
|
||||
_state.BgLayers[0].Latch();
|
||||
_state.BgLayers[1].Latch();
|
||||
_state.BgWindow.Latch();
|
||||
_state.SpriteWindow.Latch();
|
||||
_state.SpritesEnabledLatch = _state.SpritesEnabled;
|
||||
_state.DrawOutsideBgWindowLatch = _state.DrawOutsideBgWindow;
|
||||
|
||||
if(_state.Scanline > _state.LastScanline) {
|
||||
if(_state.Scanline <= 145) {
|
||||
//Support sending frame to LCD even when number of scanlines is less than the 144px resolution
|
||||
SendFrame();
|
||||
}
|
||||
_state.Mode = _state.NextMode;
|
||||
_state.Scanline = 0;
|
||||
_emu->ProcessEvent(EventType::StartFrame, CpuType::Ws);
|
||||
_currentBuffer = _currentBuffer == _outputBuffers[0] ? _outputBuffers[1] : _outputBuffers[0];
|
||||
_showIcons = _emu->GetSettings()->GetWsConfig().LcdShowIcons;
|
||||
} else if(_state.Scanline == 145) {
|
||||
SendFrame();
|
||||
} else if(_state.Scanline == 144) {
|
||||
_timer->TickVerticalTimer();
|
||||
_console->GetMemoryManager()->SetIrqSource(WsIrqSource::VerticalBlank);
|
||||
}
|
||||
|
||||
if(_state.Scanline == _state.IrqScanline) {
|
||||
_console->GetMemoryManager()->SetIrqSource(WsIrqSource::Scanline);
|
||||
}
|
||||
}
|
||||
|
||||
template<WsVideoMode mode>
|
||||
void WsPpu::DrawScanline()
|
||||
{
|
||||
uint8_t rowIndex = _state.Scanline & 0x01;
|
||||
std::fill(_rowData[rowIndex], _rowData[rowIndex] + WsConstants::ScreenWidth, PixelData{});
|
||||
|
||||
DrawSprites<mode>();
|
||||
DrawBackground<mode, 0>();
|
||||
DrawBackground<mode, 1>();
|
||||
}
|
||||
|
||||
template<WsVideoMode mode>
|
||||
void WsPpu::DrawSprites()
|
||||
{
|
||||
WsConfig& cfg = _emu->GetSettings()->GetWsConfig();
|
||||
if(!_state.SpritesEnabled || cfg.DisableSprites) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t scanline = _state.Scanline;
|
||||
uint8_t rowIndex = _state.Scanline & 0x01;
|
||||
|
||||
constexpr int tileSize = mode >= WsVideoMode::Color4bpp ? 32 : 16;
|
||||
constexpr int tileBytesPerRow = mode >= WsVideoMode::Color4bpp ? 4 : 2;
|
||||
constexpr int bank0Addr = mode >= WsVideoMode::Color4bpp ? 0x4000 : 0x2000;
|
||||
|
||||
int spriteCount = 0;
|
||||
for(int i = 0; i < _state.SpriteCountLatch; i++) {
|
||||
uint16_t addr = i * 4;
|
||||
uint8_t attributes = _spriteRam[addr + 1];
|
||||
bool highPriority = attributes & 0x20;
|
||||
|
||||
uint8_t y = _spriteRam[addr + 2];
|
||||
uint8_t tileRow = scanline - y;
|
||||
|
||||
if(tileRow < 8 && scanline < y + 8) {
|
||||
spriteCount++;
|
||||
|
||||
bool showOutsideWindow = attributes & 0x10;
|
||||
bool vMirror = attributes & 0x80;
|
||||
bool hMirror = attributes & 0x40;
|
||||
|
||||
uint16_t tileIndex = _spriteRam[addr] | ((attributes & 0x01) << 8);
|
||||
uint8_t palette = ((attributes >> 1) & 0x07) + 8;
|
||||
uint8_t sprX = _spriteRam[addr + 3];
|
||||
|
||||
if(vMirror) {
|
||||
tileRow = 7 - tileRow;
|
||||
}
|
||||
|
||||
uint16_t tileDataAddr = (bank0Addr + tileIndex * tileSize + tileRow * tileBytesPerRow);
|
||||
for(int j = 0; j < 8; j++) {
|
||||
uint8_t x = sprX + j;
|
||||
|
||||
if(x >= 224) {
|
||||
continue;
|
||||
}
|
||||
else if(_rowData[rowIndex][x].Priority > 0) {
|
||||
continue;
|
||||
}
|
||||
else if(_state.SpriteWindow.EnabledLatch && showOutsideWindow == _state.SpriteWindow.IsInsideWindow(x, scanline)) {
|
||||
//Don't draw this pixel, it's outside/inside the window and should only be drawn on the other side
|
||||
continue;
|
||||
}
|
||||
|
||||
int tileColumn = j;
|
||||
if(hMirror) {
|
||||
tileColumn = 7 - tileColumn;
|
||||
}
|
||||
|
||||
uint8_t color = GetPixelColor<mode>(tileDataAddr, tileColumn);
|
||||
if(color != 0 || (!(palette & 0x04) && mode <= WsVideoMode::Color2bpp)) {
|
||||
_rowData[rowIndex][x] = { palette, color, highPriority ? (uint8_t)2 : (uint8_t)1 };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(spriteCount >= 32) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<WsVideoMode mode, int layerIndex>
|
||||
void WsPpu::DrawBackground()
|
||||
{
|
||||
constexpr int tileSize = mode >= WsVideoMode::Color4bpp ? 32 : 16;
|
||||
constexpr int tileBytesPerRow = mode >= WsVideoMode::Color4bpp ? 4 : 2;
|
||||
constexpr int bank0Addr = mode >= WsVideoMode::Color4bpp ? 0x4000 : 0x2000;
|
||||
constexpr int bank1Addr = mode >= WsVideoMode::Color4bpp ? 0x8000 : 0x4000;
|
||||
|
||||
WsConfig& cfg = _emu->GetSettings()->GetWsConfig();
|
||||
uint8_t rowIndex = _state.Scanline & 0x01;
|
||||
WsBgLayer& layer = _state.BgLayers[layerIndex];
|
||||
if(cfg.HideBgLayers[layerIndex] || !layer.EnabledLatch) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t scanline = _state.Scanline;
|
||||
uint16_t layerAddr = (uint16_t)(layer.MapAddressLatch & (_console->GetModel() == WsModel::Monochrome ? 0x3FFF : 0x7FFF));
|
||||
|
||||
for(int cycle = 0; cycle < WsConstants::ScreenWidth; cycle++) {
|
||||
int y = (scanline + layer.ScrollYLatch) & 0xFF;
|
||||
int x = (cycle + layer.ScrollXLatch) & 0xFF;
|
||||
int row = y / 8;
|
||||
int column = x / 8;
|
||||
uint16_t tilemapAddr = layerAddr + row * 32 * 2 + column * 2;
|
||||
uint16_t tilemapData = _vram[tilemapAddr] | (_vram[tilemapAddr + 1] << 8);
|
||||
|
||||
int tileRow = y & 0x07;
|
||||
int tileColumn = x & 0x07;
|
||||
int counter = 8 - tileColumn;
|
||||
|
||||
uint16_t tileIndex = tilemapData & 0x1FF;
|
||||
uint8_t palette = (tilemapData >> 9) & 0x0F;
|
||||
bool vMirror = tilemapData & 0x8000;
|
||||
bool hMirror = tilemapData & 0x4000;
|
||||
if(hMirror) {
|
||||
tileColumn = 7 - tileColumn;
|
||||
}
|
||||
if(vMirror) {
|
||||
tileRow = 7 - tileRow;
|
||||
}
|
||||
|
||||
uint16_t tilesetAddr = mode >= WsVideoMode::Color2bpp && (tilemapData & 0x2000) ? bank1Addr : bank0Addr;
|
||||
uint16_t tileDataAddr = (tilesetAddr + tileIndex * tileSize + tileRow * tileBytesPerRow);
|
||||
|
||||
for(int i = cycle, end = std::min<int>(cycle + counter, WsConstants::ScreenWidth); i < end; i++) {
|
||||
uint8_t color = GetPixelColor<mode>(tileDataAddr, tileColumn);
|
||||
tileColumn += (hMirror ? -1 : 1);
|
||||
|
||||
if(_rowData[rowIndex][i].Priority >= layerIndex + 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if constexpr(layerIndex == 1) {
|
||||
if(_state.BgWindow.EnabledLatch && _state.DrawOutsideBgWindowLatch == _state.BgWindow.IsInsideWindow(i, scanline)) {
|
||||
//Pixel is hidden by window
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
//"In two bit per pixel modes: Palettes 0-3 and 8-11 are opaque. For these, index zero is treated as opaque."
|
||||
if(color != 0 || (!(palette & 0x04) && mode <= WsVideoMode::Color2bpp)) {
|
||||
_rowData[rowIndex][i] = { palette, color, (uint8_t)(layerIndex + 1) };
|
||||
}
|
||||
}
|
||||
|
||||
cycle += counter - 1;
|
||||
}
|
||||
}
|
||||
|
||||
template<WsVideoMode mode>
|
||||
uint16_t WsPpu::GetPixelColor(uint16_t tileAddr, uint8_t column)
|
||||
{
|
||||
switch(mode) {
|
||||
case WsVideoMode::Monochrome: {
|
||||
uint8_t tileData = _vram[tileAddr];
|
||||
uint8_t tileData2 = _vram[tileAddr + 1];
|
||||
return (
|
||||
((tileData << column) & 0x80) >> 7 |
|
||||
((tileData2 << column) & 0x80) >> 6
|
||||
);
|
||||
}
|
||||
|
||||
case WsVideoMode::Color2bpp: {
|
||||
uint8_t tileData = _vram[tileAddr];
|
||||
uint8_t tileData2 = _vram[tileAddr + 1];
|
||||
return (
|
||||
((tileData << column) & 0x80) >> 7 |
|
||||
((tileData2 << column) & 0x80) >> 6
|
||||
);
|
||||
}
|
||||
|
||||
case WsVideoMode::Color4bpp: {
|
||||
uint8_t tileData = _vram[tileAddr];
|
||||
uint8_t tileData2 = _vram[tileAddr + 1];
|
||||
uint8_t tileData3 = _vram[tileAddr + 2];
|
||||
uint8_t tileData4 = _vram[tileAddr + 3];
|
||||
return (
|
||||
((tileData << column) & 0x80) >> 7 |
|
||||
((tileData2 << column) & 0x80) >> 6 |
|
||||
((tileData3 << column) & 0x80) >> 5 |
|
||||
((tileData4 << column) & 0x80) >> 4
|
||||
);
|
||||
}
|
||||
|
||||
case WsVideoMode::Color4bppPacked: {
|
||||
return (_vram[tileAddr + column / 2] >> (column & 0x01 ? 0 : 4)) & 0x0F;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void WsPpu::ProcessSpriteCopy()
|
||||
{
|
||||
if(_state.Cycle == 0) {
|
||||
_state.SpriteCountLatch = _state.SpriteCount;
|
||||
}
|
||||
|
||||
uint16_t baseAddr = _state.SpriteTableAddress & (_console->GetModel() == WsModel::Monochrome ? 0x3FFF : 0x7FFF);
|
||||
|
||||
int i = _state.Cycle << 1;
|
||||
_spriteRam[i] = _vram[baseAddr + (((_state.FirstSpriteIndex * 4) + i) & 0x1FF)];
|
||||
_spriteRam[i+1] = _vram[baseAddr + (((_state.FirstSpriteIndex * 4) + i + 1) & 0x1FF)];
|
||||
}
|
||||
|
||||
void WsPpu::DrawIcons()
|
||||
{
|
||||
//11x11 1bpp icons (same shapes as ares)
|
||||
static constexpr uint16_t power[11] = { 0x70, 0xD8, 0x18C, 0x18C, 0x3DE, 0x3FE, 0x3FE, 0x1FC, 0x1FC, 0xF8, 0x70 };
|
||||
//static constexpr uint16_t initialized[11] = { 0, 0x3FE, 0x7FF, 0x603, 0x603, 0x603, 0x7FF, 0x7FF, 0x7FF, 0x3FE, 0 };
|
||||
static constexpr uint16_t sleep[11] = { 0x80, 0x250, 0x11B, 0x1F, 0x3E, 0xFC, 0x3C, 0x1E, 0x3E, 0x33, 0 };
|
||||
//static constexpr uint16_t lowBattery[11] = { 0x70, 0xD8, 0x88, 0x88, 0x88, 0x88, 0x88, 0x98, 0xB8, 0x88, 0xF8 };
|
||||
static constexpr uint16_t volumeWsOff[11] = { 0, 0x10, 0x18, 0x1C, 0x1E, 0x1E, 0x1E, 0x1C, 0x18, 0x10, 0 };
|
||||
static constexpr uint16_t volumeWsLow[11] = { 0, 0x10, 0x18, 0x5C, 0x9E, 0x9E, 0x9E, 0x5C, 0x18, 0x10, 0 };
|
||||
static constexpr uint16_t volumeWsHigh[11] = { 0, 0x90, 0x118, 0x25C, 0x29E, 0x29E, 0x29E, 0x25C, 0x118, 0x90, 0 };
|
||||
static constexpr uint16_t volumeWscOff[11] = { 0, 0, 0, 0, 0, 0, 0, 0x3FE, 0x1FC, 0xF8, 0x70 };
|
||||
static constexpr uint16_t volumeWscLow[11] = { 0, 0, 0, 0xF8, 0x104, 0, 0, 0x3FE, 0x1FC, 0xF8, 0x70 };
|
||||
static constexpr uint16_t volumeWscMed[11] = { 0x1FC, 0x202, 0x401, 0, 0x70, 0x88, 0, 0x3FE, 0x1FC, 0xF8, 0x70 };
|
||||
static constexpr uint16_t volumeWscHigh[11] = { 0x1FC, 0x202, 0x4F9, 0x104, 0x272, 0x88, 0, 0x3FE, 0x1FC, 0xF8, 0x70 };
|
||||
static constexpr uint16_t headphones[11] = { 0x70, 0x18C, 0x306, 0x20B, 0x41F, 0x40E, 0x447, 0x2E2, 0x370, 0x1F8, 0xD0 };
|
||||
static constexpr uint16_t verticalIcon[11] = { 0x6, 0x1E, 0x38, 0x3BF, 0x7FF, 0x7FF, 0x7FF, 0x3BF, 0x38, 0x1E, 0x6 };
|
||||
static constexpr uint16_t horizontalIcon[11] = { 0x70, 0xF8, 0xF8, 0xF8, 0x70, 0x1FC, 0x3FE, 0x3FE, 0x6FB, 0x6FB, 0xF8 };
|
||||
static constexpr uint16_t aux3[11] = { 0, 0x70, 0x1FC, 0x1FC, 0x3FE, 0x3FE, 0x3FE, 0x1FC, 0x1FC, 0x70, 0 };
|
||||
static constexpr uint16_t aux2[11] = { 0, 0, 0x70, 0xF8, 0x1FC, 0x1FC, 0x1FC, 0xF8, 0x70, 0, 0 };
|
||||
static constexpr uint16_t aux1[11] = { 0, 0, 0, 0, 0x70, 0x70, 0x70, 0, 0, 0, 0 };
|
||||
|
||||
if(_console->GetModel() == WsModel::Monochrome) {
|
||||
uint16_t* start = _currentBuffer + (WsConstants::ScreenWidth * WsConstants::ScreenHeight);
|
||||
std::fill(start, start + WsConstants::ScreenWidth * 13, 0xFFF);
|
||||
} else {
|
||||
for(int i = 0; i < WsConstants::ScreenHeight; i++) {
|
||||
uint16_t* start = _currentBuffer + (i * _screenWidth) + WsConstants::ScreenWidth;
|
||||
std::fill(start, start + 13, 0);
|
||||
}
|
||||
}
|
||||
|
||||
DrawIcon(true, power, 0);
|
||||
//DrawIcon(true, initialized, 13);
|
||||
DrawIcon(_state.Icons.Sleep, sleep, 26);
|
||||
//DrawIcon(true, lowBattery, 39, 144);
|
||||
if(_state.ShowVolumeIconFrame <= _state.FrameCount && _state.FrameCount - _state.ShowVolumeIconFrame < 128) {
|
||||
//Show speaker/headphone icons if sound button was pressed within the last 128 frames
|
||||
if(_emu->GetSettings()->GetWsConfig().AudioMode == WsAudioMode::Headphones) {
|
||||
DrawIcon(true, headphones, 65);
|
||||
} else {
|
||||
if(_console->GetModel() == WsModel::Monochrome) {
|
||||
switch(_console->GetApu()->GetMasterVolume()) {
|
||||
default: case 0: DrawIcon(true, volumeWsOff, 52); break;
|
||||
case 1: DrawIcon(true, volumeWsLow, 52); break;
|
||||
case 2: DrawIcon(true, volumeWsHigh, 52); break;
|
||||
}
|
||||
} else {
|
||||
switch(_console->GetApu()->GetMasterVolume()) {
|
||||
default: case 0: DrawIcon(true, volumeWscOff, 52); break;
|
||||
case 1: DrawIcon(true, volumeWscLow, 52); break;
|
||||
case 2: DrawIcon(true, volumeWscMed, 52); break;
|
||||
case 3: DrawIcon(true, volumeWscHigh, 52); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DrawIcon(_state.Icons.Vertical, verticalIcon, 78);
|
||||
DrawIcon(_state.Icons.Horizontal, horizontalIcon, 91);
|
||||
DrawIcon(_state.Icons.Aux3, aux3, 104);
|
||||
DrawIcon(_state.Icons.Aux2, aux2, 117);
|
||||
DrawIcon(_state.Icons.Aux1, aux1, 130);
|
||||
}
|
||||
|
||||
void WsPpu::DrawIcon(bool visible, const uint16_t icon[11], uint8_t position)
|
||||
{
|
||||
if(!visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t xPos;
|
||||
uint8_t yPos;
|
||||
uint16_t color;
|
||||
if(_console->GetModel() == WsModel::Monochrome) {
|
||||
xPos = position + 1;
|
||||
yPos = 145;
|
||||
color = 0;
|
||||
} else {
|
||||
xPos = 225;
|
||||
yPos = WsConstants::ScreenHeight - 13 - position;
|
||||
color = 0xFFF;
|
||||
}
|
||||
|
||||
for(int y = 0; y < 11; y++) {
|
||||
uint16_t basePos = (yPos + y) * _screenWidth + xPos;
|
||||
for(int x = 0; x < 11; x++) {
|
||||
if(icon[y] & (1 << x)) {
|
||||
_currentBuffer[basePos + x] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t WsPpu::GetVisibleScanlineCount()
|
||||
{
|
||||
uint16_t scanlineCount = GetScanlineCount();
|
||||
return scanlineCount <= WsConstants::ScreenHeight ? scanlineCount - 1 : WsConstants::ScreenHeight;
|
||||
}
|
||||
|
||||
uint16_t* WsPpu::GetScreenBuffer(bool prevFrame)
|
||||
{
|
||||
return prevFrame ? ((_currentBuffer == _outputBuffers[0]) ? _outputBuffers[1] : _outputBuffers[0]) : _currentBuffer;
|
||||
}
|
||||
|
||||
void WsPpu::DebugSendFrame()
|
||||
{
|
||||
int offset = std::max(0, (int)(_state.Cycle + (_state.Scanline - 1) * _screenWidth));
|
||||
int pixelsToClear = WsConstants::MaxPixelCount - offset;
|
||||
if(pixelsToClear > 0) {
|
||||
memset(_currentBuffer + offset, 0, pixelsToClear * sizeof(uint16_t));
|
||||
}
|
||||
|
||||
uint16_t width = _showIcons ? _screenWidth : WsConstants::ScreenWidth;
|
||||
uint16_t height = _showIcons ? _screenHeight : WsConstants::ScreenHeight;
|
||||
RenderedFrame frame(_currentBuffer, width, height, 1.0, _state.FrameCount);
|
||||
_emu->GetVideoDecoder()->UpdateFrame(frame, false, false);
|
||||
}
|
||||
|
||||
void WsPpu::SetOutputToBgColor()
|
||||
{
|
||||
std::fill(_currentBuffer, _currentBuffer + WsConstants::MaxPixelCount, GetBgColor());
|
||||
}
|
||||
|
||||
void WsPpu::ShowVolumeIcon()
|
||||
{
|
||||
_state.ShowVolumeIconFrame = _state.FrameCount;
|
||||
}
|
||||
|
||||
uint8_t WsPpu::GetLcdStatus()
|
||||
{
|
||||
uint8_t masterVolume = _console->GetApu()->GetMasterVolume();
|
||||
uint8_t volumeLevel;
|
||||
|
||||
if(_console->GetModel() == WsModel::Monochrome) {
|
||||
switch(masterVolume) {
|
||||
default: case 0: volumeLevel = 0; break;
|
||||
case 1: volumeLevel = 2; break;
|
||||
case 2: volumeLevel = 3; break;
|
||||
}
|
||||
} else {
|
||||
switch(masterVolume) {
|
||||
default: case 0: volumeLevel = 0; break;
|
||||
case 1: volumeLevel = 2; break;
|
||||
case 2: volumeLevel = 1; break;
|
||||
case 3: volumeLevel = 3; break;
|
||||
}
|
||||
}
|
||||
|
||||
bool headphone = false;
|
||||
bool speaker = false;
|
||||
if(_state.ShowVolumeIconFrame <= _state.FrameCount && _state.FrameCount - _state.ShowVolumeIconFrame < 128) {
|
||||
//Show speaker/headphone icons if sound button was pressed within the last 128 frames
|
||||
if(_emu->GetSettings()->GetWsConfig().AudioMode == WsAudioMode::Headphones) {
|
||||
headphone = true;
|
||||
} else {
|
||||
speaker = true;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
(_state.SleepEnabled ? 0x01 : 0) |
|
||||
(headphone ? 0x02 : 0) |
|
||||
(volumeLevel << 2) |
|
||||
(speaker ? 0x10 : 0)
|
||||
);
|
||||
}
|
||||
|
||||
void WsPpu::SendFrame()
|
||||
{
|
||||
if(_state.SleepEnabled || !_state.LcdEnabled || _state.LastScanline == 255) {
|
||||
//Screen should be white when in sleep mode, or if the last scanline is set to 255
|
||||
std::fill(_currentBuffer, _currentBuffer + WsConstants::MaxPixelCount, 0xFFF);
|
||||
} else if(_state.LastScanline < 144) {
|
||||
//Clear everything after the last scanline (results in less than 144 visible scanlines)
|
||||
std::fill(_currentBuffer + _state.LastScanline * _screenWidth, _currentBuffer + WsConstants::MaxPixelCount, 0xFFF);
|
||||
}
|
||||
|
||||
if(_showIcons) {
|
||||
DrawIcons();
|
||||
}
|
||||
|
||||
_emu->ProcessEvent(EventType::EndFrame, CpuType::Ws);
|
||||
_state.FrameCount++;
|
||||
|
||||
_emu->GetNotificationManager()->SendNotification(ConsoleNotificationType::PpuFrameDone);
|
||||
|
||||
uint16_t width = _showIcons ? _screenWidth : WsConstants::ScreenWidth;
|
||||
uint16_t height = _showIcons ? _screenHeight : WsConstants::ScreenHeight;
|
||||
RenderedFrame frame(_currentBuffer, width, height, 1.0, _state.FrameCount, _console->GetControlManager()->GetPortStates());
|
||||
bool rewinding = _emu->GetRewindManager()->IsRewinding();
|
||||
_emu->GetVideoDecoder()->UpdateFrame(frame, rewinding, rewinding);
|
||||
|
||||
_emu->ProcessEndOfFrame();
|
||||
_console->ProcessEndOfFrame();
|
||||
}
|
||||
|
||||
uint8_t WsPpu::ReadPort(uint16_t port)
|
||||
{
|
||||
switch(port) {
|
||||
case 0x00: return _state.Control;
|
||||
case 0x01: return _state.BgColor & (_console->GetModel() == WsModel::Monochrome ? 0x07 : 0xFF);
|
||||
case 0x02: return _state.Scanline;
|
||||
case 0x03: return _state.IrqScanline;
|
||||
case 0x04: return (_state.SpriteTableAddress >> 9) & (_console->GetModel() == WsModel::Monochrome ? 0x1F : 0x3F);
|
||||
case 0x05: return _state.FirstSpriteIndex;
|
||||
case 0x06: return _state.SpriteCount;
|
||||
case 0x07: return _state.ScreenAddress & (_console->GetModel() == WsModel::Monochrome ? 0x77 : 0xFF);
|
||||
case 0x08: return _state.BgWindow.Left;
|
||||
case 0x09: return _state.BgWindow.Top;
|
||||
case 0x0A: return _state.BgWindow.Right;
|
||||
case 0x0B: return _state.BgWindow.Bottom;
|
||||
|
||||
case 0x0C: return _state.SpriteWindow.Left;
|
||||
case 0x0D: return _state.SpriteWindow.Top;
|
||||
case 0x0E: return _state.SpriteWindow.Right;
|
||||
case 0x0F: return _state.SpriteWindow.Bottom;
|
||||
|
||||
case 0x10: return _state.BgLayers[0].ScrollX;
|
||||
case 0x11: return _state.BgLayers[0].ScrollY;
|
||||
case 0x12: return _state.BgLayers[1].ScrollX;
|
||||
case 0x13: return _state.BgLayers[1].ScrollY;
|
||||
|
||||
case 0x14:
|
||||
return (
|
||||
(_state.LcdEnabled ? 0x01 : 0) |
|
||||
(_state.HighContrast ? 0x02 : 0)
|
||||
);
|
||||
|
||||
case 0x15: return _state.Icons.Value;
|
||||
case 0x16: return _state.LastScanline;
|
||||
case 0x17: return _state.BackPorchScanline;
|
||||
case 0x1A: return GetLcdStatus();
|
||||
|
||||
case 0x1C:
|
||||
case 0x1D:
|
||||
case 0x1E:
|
||||
case 0x1F:
|
||||
return (
|
||||
_state.BwShades[(port - 0x1C) * 2] |
|
||||
(_state.BwShades[(port - 0x1C) * 2 + 1] << 4)
|
||||
);
|
||||
|
||||
default:
|
||||
if(port >= 0x20 && port <= 0x3F) {
|
||||
return (
|
||||
_state.BwPalettes[(port - 0x20) * 2] |
|
||||
(_state.BwPalettes[(port - 0x20) * 2 + 1] << 4)
|
||||
);
|
||||
}
|
||||
else {
|
||||
LogDebug("[Debug] PPU Read - missing handler: $" + HexUtilities::ToHex(port));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WsPpu::WritePort(uint16_t port, uint8_t value)
|
||||
{
|
||||
switch(port) {
|
||||
case 0x00:
|
||||
_state.Control = value & 0x3F;
|
||||
_state.BgLayers[0].Enabled = value & 0x01;
|
||||
_state.BgLayers[1].Enabled = value & 0x02;
|
||||
_state.SpritesEnabled = value & 0x04;
|
||||
_state.SpriteWindow.Enabled = value & 0x08;
|
||||
_state.DrawOutsideBgWindow = value & 0x10;
|
||||
_state.BgWindow.Enabled = value & 0x20;
|
||||
break;
|
||||
|
||||
case 0x01: _state.BgColor = value & (_console->GetModel() == WsModel::Monochrome ? 0x07 : 0xFF); break;
|
||||
case 0x03: _state.IrqScanline = value; break;
|
||||
case 0x04: _state.SpriteTableAddress = (value & (_console->GetModel() == WsModel::Monochrome ? 0x1F : 0x3F)) << 9; break;
|
||||
|
||||
case 0x05: _state.FirstSpriteIndex = value & 0x7F; break;
|
||||
case 0x06: _state.SpriteCount = value; break;
|
||||
|
||||
case 0x07:
|
||||
_state.ScreenAddress = value & (_console->GetModel() == WsModel::Monochrome ? 0x77 : 0xFF);
|
||||
_state.BgLayers[0].MapAddress = (_state.ScreenAddress & 0x0F) << 11;
|
||||
_state.BgLayers[1].MapAddress = (_state.ScreenAddress & 0xF0) << 7;
|
||||
break;
|
||||
|
||||
case 0x08: _state.BgWindow.Left = value; break;
|
||||
case 0x09: _state.BgWindow.Top = value; break;
|
||||
case 0x0A: _state.BgWindow.Right = value; break;
|
||||
case 0x0B: _state.BgWindow.Bottom = value; break;
|
||||
|
||||
case 0x0C: _state.SpriteWindow.Left = value; break;
|
||||
case 0x0D: _state.SpriteWindow.Top = value; break;
|
||||
case 0x0E: _state.SpriteWindow.Right = value; break;
|
||||
case 0x0F: _state.SpriteWindow.Bottom = value; break;
|
||||
|
||||
case 0x10: _state.BgLayers[0].ScrollX = value; break;
|
||||
case 0x11: _state.BgLayers[0].ScrollY = value; break;
|
||||
case 0x12: _state.BgLayers[1].ScrollX = value; break;
|
||||
case 0x13: _state.BgLayers[1].ScrollY = value; break;
|
||||
|
||||
case 0x14:
|
||||
_state.LcdEnabled = value & 0x01;
|
||||
if(_console->GetModel() == WsModel::Color) {
|
||||
_state.HighContrast = value & 0x02;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x15:
|
||||
_state.Icons.Sleep = value & 0x01;
|
||||
_state.Icons.Vertical = value & 0x02;
|
||||
_state.Icons.Horizontal = value & 0x04;
|
||||
_state.Icons.Aux1 = value & 0x08;
|
||||
_state.Icons.Aux2 = value & 0x10;
|
||||
_state.Icons.Aux3 = value & 0x20;
|
||||
_state.Icons.Value = value & 0x3F;
|
||||
break;
|
||||
|
||||
case 0x16: _state.LastScanline = value; break;
|
||||
|
||||
case 0x17:
|
||||
if(_console->GetModel() == WsModel::Color) {
|
||||
_state.BackPorchScanline = value; break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x1A:
|
||||
_state.SleepEnabled = value & 0x01;
|
||||
break;
|
||||
|
||||
case 0x1C:
|
||||
case 0x1D:
|
||||
case 0x1E:
|
||||
case 0x1F:
|
||||
_state.BwShades[(port - 0x1C) * 2] = value & 0x0F;
|
||||
_state.BwShades[(port - 0x1C) * 2 + 1] = (value >> 4) & 0x0F;
|
||||
break;
|
||||
|
||||
default:
|
||||
if(port >= 0x20 && port <= 0x3F) {
|
||||
_state.BwPalettes[(port - 0x20) * 2] = value & 0x07;
|
||||
_state.BwPalettes[(port - 0x20) * 2 + 1] = (value >> 4) & 0x07;
|
||||
}
|
||||
else {
|
||||
LogDebug("[Debug] PPU Write - missing handler: $" + HexUtilities::ToHex(port) + " = " + HexUtilities::ToHex(value));
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t WsPpu::ReadLcdConfigPort(uint16_t port)
|
||||
{
|
||||
return _state.LcdTftConfig[port - 0x70];
|
||||
}
|
||||
|
||||
void WsPpu::WriteLcdConfigPort(uint16_t port, uint8_t value)
|
||||
{
|
||||
_state.LcdTftConfig[port - 0x70] = value;
|
||||
}
|
||||
|
||||
void WsPpu::Serialize(Serializer& s)
|
||||
{
|
||||
SV(_state.FrameCount);
|
||||
SV(_state.Cycle);
|
||||
SV(_state.Scanline);
|
||||
SV(_state.LastScanline);
|
||||
|
||||
for(int i = 0; i < 2; i++) {
|
||||
SVI(_state.BgLayers[i].Enabled);
|
||||
SVI(_state.BgLayers[i].EnabledLatch);
|
||||
SVI(_state.BgLayers[i].MapAddress);
|
||||
SVI(_state.BgLayers[i].MapAddressLatch);
|
||||
SVI(_state.BgLayers[i].ScrollX);
|
||||
SVI(_state.BgLayers[i].ScrollXLatch);
|
||||
SVI(_state.BgLayers[i].ScrollY);
|
||||
SVI(_state.BgLayers[i].ScrollYLatch);
|
||||
}
|
||||
|
||||
SV(_state.BgWindow.Enabled);
|
||||
SV(_state.BgWindow.EnabledLatch);
|
||||
SV(_state.BgWindow.Bottom);
|
||||
SV(_state.BgWindow.BottomLatch);
|
||||
SV(_state.BgWindow.Top);
|
||||
SV(_state.BgWindow.TopLatch);
|
||||
SV(_state.BgWindow.Left);
|
||||
SV(_state.BgWindow.LeftLatch);
|
||||
SV(_state.BgWindow.Right);
|
||||
SV(_state.BgWindow.RightLatch);
|
||||
|
||||
SV(_state.SpriteWindow.Enabled);
|
||||
SV(_state.SpriteWindow.EnabledLatch);
|
||||
SV(_state.SpriteWindow.Bottom);
|
||||
SV(_state.SpriteWindow.BottomLatch);
|
||||
SV(_state.SpriteWindow.Top);
|
||||
SV(_state.SpriteWindow.TopLatch);
|
||||
SV(_state.SpriteWindow.Left);
|
||||
SV(_state.SpriteWindow.LeftLatch);
|
||||
SV(_state.SpriteWindow.Right);
|
||||
SV(_state.SpriteWindow.RightLatch);
|
||||
|
||||
SV(_state.DrawOutsideBgWindow);
|
||||
SV(_state.DrawOutsideBgWindowLatch);
|
||||
|
||||
SVArray(_state.BwPalettes, 0x40);
|
||||
SVArray(_state.BwShades, 8);
|
||||
SVArray(_spriteRam, 512);
|
||||
SVArray(_state.LcdTftConfig, 8);
|
||||
|
||||
SV(_state.SpriteTableAddress);
|
||||
SV(_state.FirstSpriteIndex);
|
||||
SV(_state.SpriteCount);
|
||||
SV(_state.SpriteCountLatch);
|
||||
SV(_state.SpritesEnabled);
|
||||
SV(_state.SpritesEnabledLatch);
|
||||
SV(_state.Mode);
|
||||
SV(_state.NextMode);
|
||||
|
||||
SV(_state.BgColor);
|
||||
SV(_state.IrqScanline);
|
||||
|
||||
SV(_state.Icons.Sleep);
|
||||
SV(_state.Icons.Horizontal);
|
||||
SV(_state.Icons.Vertical);
|
||||
SV(_state.Icons.Aux1);
|
||||
SV(_state.Icons.Aux2);
|
||||
SV(_state.Icons.Aux3);
|
||||
SV(_state.Icons.Value);
|
||||
|
||||
SV(_state.Control);
|
||||
SV(_state.ScreenAddress);
|
||||
|
||||
SV(_state.LcdEnabled);
|
||||
SV(_state.HighContrast);
|
||||
SV(_state.SleepEnabled);
|
||||
|
||||
SV(_state.ShowVolumeIconFrame);
|
||||
SV(_state.BackPorchScanline);
|
||||
}
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
#pragma once
|
||||
#include "pch.h"
|
||||
#include "WS/WsTypes.h"
|
||||
#include "Shared/Emulator.h"
|
||||
#include "Shared/SettingTypes.h"
|
||||
#include "Utilities/ISerializable.h"
|
||||
|
||||
class Emulator;
|
||||
class WsTimer;
|
||||
class WsConsole;
|
||||
|
||||
class WsPpu final : public ISerializable
|
||||
{
|
||||
private:
|
||||
WsPpuState _state = {};
|
||||
Emulator* _emu = nullptr;
|
||||
WsConsole* _console = nullptr;
|
||||
WsTimer* _timer = nullptr;
|
||||
uint16_t* _outputBuffers[2] = {};
|
||||
uint16_t* _currentBuffer = nullptr;
|
||||
uint8_t* _vram = nullptr;
|
||||
|
||||
uint8_t _spriteRam[512] = {};
|
||||
|
||||
struct PixelData
|
||||
{
|
||||
uint8_t Palette;
|
||||
uint8_t Color;
|
||||
uint8_t Priority;
|
||||
};
|
||||
|
||||
PixelData _rowData[2][224];
|
||||
|
||||
uint16_t _screenHeight = 0;
|
||||
uint16_t _screenWidth = 0;
|
||||
bool _showIcons = false;
|
||||
|
||||
void ProcessEndOfScanline();
|
||||
void ProcessSpriteCopy();
|
||||
|
||||
void DrawIcons();
|
||||
void DrawIcon(bool visible, const uint16_t icon[11], uint8_t position);
|
||||
uint8_t GetLcdStatus();
|
||||
|
||||
void SendFrame();
|
||||
|
||||
template<WsVideoMode mode> void DrawScanline();
|
||||
template<WsVideoMode mode> void DrawSprites();
|
||||
template<WsVideoMode mode, int layerIndex> void DrawBackground();
|
||||
|
||||
template<WsVideoMode mode> __forceinline uint16_t GetPixelColor(uint16_t tileAddr, uint8_t column);
|
||||
|
||||
__forceinline uint16_t GetBgColor()
|
||||
{
|
||||
if(_state.Mode == WsVideoMode::Monochrome) {
|
||||
uint8_t bgBrightness = _state.BwShades[_state.BgColor & 0x07] ^ 0x0F;
|
||||
return bgBrightness | (bgBrightness << 4) | (bgBrightness << 8);
|
||||
} else {
|
||||
return _vram[0xFE00 | (_state.BgColor << 1)] | ((_vram[0xFE00 | (_state.BgColor << 1) | 1] & 0x0F) << 8);
|
||||
}
|
||||
}
|
||||
|
||||
__forceinline uint16_t GetPixelRgbColor(WsVideoMode mode, uint8_t color, uint8_t palette)
|
||||
{
|
||||
switch(mode) {
|
||||
case WsVideoMode::Monochrome:
|
||||
{
|
||||
uint8_t shadeValue = _state.BwPalettes[(palette << 2) | color];
|
||||
uint8_t brightness = _state.BwShades[shadeValue] ^ 0x0F;
|
||||
return brightness | (brightness << 4) | (brightness << 8);
|
||||
}
|
||||
|
||||
case WsVideoMode::Color2bpp:
|
||||
case WsVideoMode::Color4bpp:
|
||||
case WsVideoMode::Color4bppPacked:
|
||||
{
|
||||
uint16_t paletteAddr = 0xFE00 | (palette << 5) | (color << 1);
|
||||
return _vram[paletteAddr] | ((_vram[paletteAddr + 1] & 0x0F) << 8);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ProcessHblank();
|
||||
|
||||
public:
|
||||
WsPpu(Emulator* emu, WsConsole* console, WsTimer* timer, uint8_t* vram);
|
||||
~WsPpu();
|
||||
|
||||
__forceinline void Exec()
|
||||
{
|
||||
if(_state.Scanline == WsConstants::ScreenHeight) {
|
||||
ProcessSpriteCopy();
|
||||
}
|
||||
|
||||
if(_state.Cycle < 224) {
|
||||
if(_state.Scanline < WsConstants::ScreenHeight + 1 && _state.Scanline > 0) {
|
||||
//Palette lookup + output pixel on the first 224 cycles
|
||||
uint8_t rowIndex = (_state.Scanline & 0x01) ^ 1;
|
||||
PixelData& data = _rowData[rowIndex][_state.Cycle];
|
||||
uint16_t screenWidth = _showIcons ? _screenWidth : WsConstants::ScreenWidth;
|
||||
uint16_t offset = (_state.Scanline - 1) * screenWidth + _state.Cycle;
|
||||
_currentBuffer[offset] = data.Priority == 0 ? GetBgColor() : GetPixelRgbColor(_state.Mode, data.Color, data.Palette);
|
||||
}
|
||||
} else {
|
||||
if(_state.Cycle == 224) {
|
||||
ProcessHblank();
|
||||
} else if(_state.Cycle == 255) {
|
||||
ProcessEndOfScanline();
|
||||
_state.Cycle = -1;
|
||||
}
|
||||
}
|
||||
|
||||
_state.Cycle++;
|
||||
_emu->ProcessPpuCycle<CpuType::Ws>();
|
||||
}
|
||||
|
||||
void SetVideoMode(WsVideoMode mode);
|
||||
|
||||
uint8_t ReadPort(uint16_t port);
|
||||
void WritePort(uint16_t port, uint8_t value);
|
||||
|
||||
uint8_t ReadLcdConfigPort(uint16_t port);
|
||||
void WriteLcdConfigPort(uint16_t port, uint8_t value);
|
||||
|
||||
uint16_t GetCycle() { return _state.Cycle; }
|
||||
uint16_t GetScanline() { return _state.Scanline; }
|
||||
uint16_t GetScanlineCount() { return _state.LastScanline + 1; }
|
||||
uint32_t GetFrameCount() { return _state.FrameCount; }
|
||||
uint16_t GetScreenWidth() { return _showIcons ? _screenWidth : WsConstants::ScreenWidth; }
|
||||
uint16_t GetScreenHeight() { return _showIcons ? _screenHeight : WsConstants::ScreenHeight; }
|
||||
WsPpuState& GetState() { return _state; }
|
||||
|
||||
uint16_t GetVisibleScanlineCount();
|
||||
uint16_t* GetScreenBuffer(bool prevFrame);
|
||||
|
||||
void DebugSendFrame();
|
||||
void SetOutputToBgColor();
|
||||
|
||||
void ShowVolumeIcon();
|
||||
|
||||
void Serialize(Serializer& s) override;
|
||||
};
|
||||
@@ -0,0 +1,80 @@
|
||||
#include "pch.h"
|
||||
#include "WS/WsSerial.h"
|
||||
#include "WS/WsConsole.h"
|
||||
#include "Utilities/Serializer.h"
|
||||
|
||||
WsSerial::WsSerial(WsConsole* console)
|
||||
{
|
||||
_console = console;
|
||||
}
|
||||
|
||||
uint8_t WsSerial::Read(uint16_t port)
|
||||
{
|
||||
switch(port) {
|
||||
case 0xB1: return _state.ReceiveBuffer;
|
||||
|
||||
case 0xB3:
|
||||
UpdateState();
|
||||
return (
|
||||
(_state.HasReceiveData ? 0x01 : 0) |
|
||||
(_state.ReceiveOverflow ? 0x02 : 0) |
|
||||
(_state.HasSendData ? 0 : 0x04) |
|
||||
(_state.HighSpeed ? 0x40 : 0) |
|
||||
(_state.Enabled ? 0x80 : 0)
|
||||
);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void WsSerial::Write(uint16_t port, uint8_t value)
|
||||
{
|
||||
switch(port) {
|
||||
case 0xB1:
|
||||
_state.SendBuffer = value;
|
||||
_state.HasSendData = true;
|
||||
_state.SendClock = _console->GetMasterClock();
|
||||
break;
|
||||
|
||||
case 0xB3:
|
||||
_state.Enabled = value & 0x80;
|
||||
_state.HighSpeed = value & 0x40;
|
||||
if(value & 0x20) {
|
||||
_state.ReceiveOverflow = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void WsSerial::UpdateState()
|
||||
{
|
||||
if(_state.HasSendData) {
|
||||
//At 9600 bauds/s, it takes 3200 master clocks to send 1 byte
|
||||
//At 38400 (high speed mode), it takes 800 master clocks
|
||||
int cyclesPerByte = _state.HighSpeed ? 800 : 3200;
|
||||
uint64_t cyclesElapsed = _console->GetMasterClock() - _state.SendClock;
|
||||
if(cyclesElapsed > cyclesPerByte) {
|
||||
_state.HasSendData = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool WsSerial::HasSendIrq()
|
||||
{
|
||||
if(_state.HasSendData) {
|
||||
UpdateState();
|
||||
}
|
||||
return _state.Enabled && !_state.HasSendData;
|
||||
}
|
||||
|
||||
void WsSerial::Serialize(Serializer& s)
|
||||
{
|
||||
SV(_state.Enabled);
|
||||
SV(_state.HighSpeed);
|
||||
SV(_state.HasReceiveData);
|
||||
SV(_state.HasSendData);
|
||||
SV(_state.SendBuffer);
|
||||
SV(_state.SendClock);
|
||||
SV(_state.ReceiveBuffer);
|
||||
SV(_state.ReceiveOverflow);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
#include "pch.h"
|
||||
#include "WS/WsTypes.h"
|
||||
#include "Utilities/ISerializable.h"
|
||||
|
||||
class WsConsole;
|
||||
|
||||
class WsSerial final : public ISerializable
|
||||
{
|
||||
private:
|
||||
WsSerialState _state = {};
|
||||
WsConsole* _console = nullptr;
|
||||
|
||||
void UpdateState();
|
||||
|
||||
public:
|
||||
WsSerial(WsConsole* console);
|
||||
|
||||
WsSerialState& GetState() { return _state; }
|
||||
|
||||
uint8_t Read(uint16_t port);
|
||||
void Write(uint16_t port, uint8_t value);
|
||||
|
||||
bool HasSendIrq();
|
||||
|
||||
void Serialize(Serializer& s) override;
|
||||
};
|
||||
@@ -0,0 +1,98 @@
|
||||
#include "pch.h"
|
||||
#include "WS/WsTimer.h"
|
||||
#include "WS/WsMemoryManager.h"
|
||||
#include "Utilities/BitUtilities.h"
|
||||
|
||||
void WsTimer::Init(WsMemoryManager* memoryManager)
|
||||
{
|
||||
_memoryManager = memoryManager;
|
||||
}
|
||||
|
||||
void WsTimer::TickHorizontalTimer()
|
||||
{
|
||||
if(_state.HTimer == 1) {
|
||||
_memoryManager->SetIrqSource(WsIrqSource::HorizontalBlankTimer);
|
||||
}
|
||||
|
||||
if(_state.HBlankEnabled) {
|
||||
if(_state.HTimer && !--_state.HTimer && _state.HBlankAutoReload) {
|
||||
_state.HTimer = _state.HReloadValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WsTimer::TickVerticalTimer()
|
||||
{
|
||||
if(_state.VTimer == 1) {
|
||||
_memoryManager->SetIrqSource(WsIrqSource::VerticalBlankTimer);
|
||||
}
|
||||
|
||||
if(_state.VBlankEnabled) {
|
||||
if(_state.VTimer && !--_state.VTimer && _state.VBlankAutoReload) {
|
||||
_state.VTimer = _state.VReloadValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t WsTimer::ReadPort(uint16_t port)
|
||||
{
|
||||
switch(port) {
|
||||
case 0xA2: return _state.Control;
|
||||
case 0xA4: return BitUtilities::GetBits<0>(_state.HReloadValue); break;
|
||||
case 0xA5: return BitUtilities::GetBits<8>(_state.HReloadValue); break;
|
||||
case 0xA6: return BitUtilities::GetBits<0>(_state.VReloadValue); break;
|
||||
case 0xA7: return BitUtilities::GetBits<8>(_state.VReloadValue); break;
|
||||
case 0xA8: return BitUtilities::GetBits<0>(_state.HTimer); break;
|
||||
case 0xA9: return BitUtilities::GetBits<8>(_state.HTimer); break;
|
||||
case 0xAA: return BitUtilities::GetBits<0>(_state.VTimer); break;
|
||||
case 0xAB: return BitUtilities::GetBits<8>(_state.VTimer); break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void WsTimer::WritePort(uint16_t port, uint8_t value)
|
||||
{
|
||||
switch(port) {
|
||||
case 0xA2:
|
||||
_state.Control = value & 0x0F;
|
||||
_state.HBlankEnabled = value & 0x01;
|
||||
_state.HBlankAutoReload = value & 0x02;
|
||||
_state.VBlankEnabled = value & 0x04;
|
||||
_state.VBlankAutoReload = value & 0x08;
|
||||
break;
|
||||
|
||||
case 0xA4:
|
||||
BitUtilities::SetBits<0>(_state.HReloadValue, value);
|
||||
_state.HTimer = _state.HReloadValue;
|
||||
break;
|
||||
|
||||
case 0xA5:
|
||||
BitUtilities::SetBits<8>(_state.HReloadValue, value);
|
||||
_state.HTimer = _state.HReloadValue;
|
||||
break;
|
||||
|
||||
case 0xA6:
|
||||
BitUtilities::SetBits<0>(_state.VReloadValue, value);
|
||||
_state.VTimer = _state.VReloadValue;
|
||||
break;
|
||||
|
||||
case 0xA7:
|
||||
BitUtilities::SetBits<8>(_state.VReloadValue, value);
|
||||
_state.VTimer = _state.VReloadValue;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void WsTimer::Serialize(Serializer& s)
|
||||
{
|
||||
SV(_state.HTimer);
|
||||
SV(_state.VTimer);
|
||||
SV(_state.HReloadValue);
|
||||
SV(_state.VReloadValue);
|
||||
SV(_state.Control);
|
||||
SV(_state.HBlankEnabled);
|
||||
SV(_state.HBlankAutoReload);
|
||||
SV(_state.VBlankEnabled);
|
||||
SV(_state.VBlankAutoReload);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
#include "pch.h"
|
||||
#include "WS/WsTypes.h"
|
||||
#include "Utilities/ISerializable.h"
|
||||
|
||||
class WsMemoryManager;
|
||||
|
||||
class WsTimer final : public ISerializable
|
||||
{
|
||||
private:
|
||||
WsMemoryManager* _memoryManager = nullptr;
|
||||
WsTimerState _state = {};
|
||||
|
||||
public:
|
||||
void Init(WsMemoryManager* memoryManager);
|
||||
WsTimerState& GetState() { return _state; }
|
||||
|
||||
void TickHorizontalTimer();
|
||||
void TickVerticalTimer();
|
||||
|
||||
uint8_t ReadPort(uint16_t port);
|
||||
void WritePort(uint16_t port, uint8_t value);
|
||||
|
||||
void Serialize(Serializer& s) override;
|
||||
};
|
||||
@@ -0,0 +1,479 @@
|
||||
#pragma once
|
||||
#include "pch.h"
|
||||
#include "Shared/BaseState.h"
|
||||
#include "Shared/SettingTypes.h"
|
||||
|
||||
class WsConstants
|
||||
{
|
||||
public:
|
||||
static constexpr uint32_t ScreenWidth = 224;
|
||||
static constexpr uint32_t ScreenHeight = 144;
|
||||
static constexpr uint32_t ClocksPerScanline = 256;
|
||||
static constexpr uint32_t ScanlineCount = 159;
|
||||
static constexpr uint32_t PixelCount = WsConstants::ScreenWidth * WsConstants::ScreenHeight;
|
||||
static constexpr uint32_t MaxPixelCount = WsConstants::ScreenWidth * (WsConstants::ScreenHeight + 13);
|
||||
};
|
||||
|
||||
struct WsCpuFlags
|
||||
{
|
||||
bool Carry; //0x01
|
||||
bool Parity; //0x04
|
||||
bool AuxCarry; //0x10
|
||||
bool Zero; //0x40
|
||||
bool Sign; //0x80
|
||||
bool Trap; //0x100
|
||||
bool Irq; //0x200
|
||||
bool Direction; //0x400
|
||||
bool Overflow; //0x800
|
||||
bool Mode; //0x8000
|
||||
|
||||
uint16_t Get()
|
||||
{
|
||||
return (
|
||||
(uint8_t)Carry |
|
||||
((uint8_t)Parity << 2) |
|
||||
((uint8_t)AuxCarry << 4) |
|
||||
((uint8_t)Zero << 6) |
|
||||
((uint8_t)Sign << 7) |
|
||||
((uint8_t)Trap << 8) |
|
||||
((uint8_t)Irq << 9) |
|
||||
((uint8_t)Direction << 10) |
|
||||
((uint8_t)Overflow << 11) |
|
||||
((uint8_t)Mode << 15) |
|
||||
0x7002
|
||||
);
|
||||
}
|
||||
|
||||
void Set(uint16_t f)
|
||||
{
|
||||
Carry = f & 0x01;
|
||||
Parity = f & 0x04;
|
||||
AuxCarry = f & 0x10;
|
||||
Zero = f & 0x40;
|
||||
Sign = f & 0x80;
|
||||
Trap = f & 0x100;
|
||||
Irq = f & 0x200;
|
||||
Direction = f & 0x400;
|
||||
Overflow = f & 0x800;
|
||||
Mode = f & 0x8000;
|
||||
}
|
||||
};
|
||||
|
||||
struct WsCpuState : BaseState
|
||||
{
|
||||
uint64_t CycleCount;
|
||||
|
||||
uint16_t CS;
|
||||
uint16_t IP;
|
||||
|
||||
uint16_t SS;
|
||||
uint16_t SP;
|
||||
uint16_t BP;
|
||||
|
||||
uint16_t DS;
|
||||
uint16_t ES;
|
||||
|
||||
uint16_t SI;
|
||||
uint16_t DI;
|
||||
|
||||
uint16_t AX;
|
||||
uint16_t BX;
|
||||
uint16_t CX;
|
||||
uint16_t DX;
|
||||
|
||||
WsCpuFlags Flags;
|
||||
bool Halted;
|
||||
};
|
||||
|
||||
struct WsBgLayer
|
||||
{
|
||||
uint16_t MapAddress;
|
||||
uint16_t MapAddressLatch;
|
||||
|
||||
uint8_t ScrollX;
|
||||
uint8_t ScrollXLatch;
|
||||
|
||||
uint8_t ScrollY;
|
||||
uint8_t ScrollYLatch;
|
||||
|
||||
bool Enabled;
|
||||
bool EnabledLatch;
|
||||
|
||||
void Latch()
|
||||
{
|
||||
EnabledLatch = Enabled;
|
||||
ScrollXLatch = ScrollX;
|
||||
ScrollYLatch = ScrollY;
|
||||
MapAddressLatch = MapAddress;
|
||||
}
|
||||
};
|
||||
|
||||
struct WsWindow
|
||||
{
|
||||
bool Enabled;
|
||||
bool EnabledLatch;
|
||||
|
||||
uint8_t Left;
|
||||
uint8_t LeftLatch;
|
||||
uint8_t Right;
|
||||
uint8_t RightLatch;
|
||||
uint8_t Top;
|
||||
uint8_t TopLatch;
|
||||
uint8_t Bottom;
|
||||
uint8_t BottomLatch;
|
||||
|
||||
bool IsInsideWindow(uint8_t x, uint8_t y) { return x >= LeftLatch && x <= RightLatch && y >= TopLatch && y <= BottomLatch; }
|
||||
|
||||
void Latch()
|
||||
{
|
||||
EnabledLatch = Enabled;
|
||||
LeftLatch = Left;
|
||||
RightLatch = Right;
|
||||
TopLatch = Top;
|
||||
BottomLatch = Bottom;
|
||||
}
|
||||
};
|
||||
|
||||
struct WsLcdIcons
|
||||
{
|
||||
bool Sleep;
|
||||
bool Vertical;
|
||||
bool Horizontal;
|
||||
bool Aux1;
|
||||
bool Aux2;
|
||||
bool Aux3;
|
||||
|
||||
uint8_t Value;
|
||||
};
|
||||
|
||||
enum class WsVideoMode : uint8_t
|
||||
{
|
||||
Monochrome,
|
||||
Color2bpp,
|
||||
Color4bpp,
|
||||
Color4bppPacked
|
||||
};
|
||||
|
||||
struct WsPpuState : BaseState
|
||||
{
|
||||
uint32_t FrameCount;
|
||||
uint16_t Cycle;
|
||||
uint16_t Scanline;
|
||||
|
||||
WsBgLayer BgLayers[2];
|
||||
WsWindow BgWindow;
|
||||
WsWindow SpriteWindow;
|
||||
bool DrawOutsideBgWindow;
|
||||
bool DrawOutsideBgWindowLatch;
|
||||
|
||||
uint8_t BwPalettes[0x20 * 2];
|
||||
uint8_t BwShades[8];
|
||||
|
||||
uint16_t SpriteTableAddress;
|
||||
uint8_t FirstSpriteIndex;
|
||||
uint8_t SpriteCount;
|
||||
uint8_t SpriteCountLatch;
|
||||
bool SpritesEnabled;
|
||||
bool SpritesEnabledLatch;
|
||||
|
||||
WsVideoMode Mode;
|
||||
WsVideoMode NextMode;
|
||||
|
||||
uint8_t BgColor;
|
||||
uint8_t IrqScanline;
|
||||
|
||||
bool LcdEnabled;
|
||||
bool HighContrast;
|
||||
bool SleepEnabled;
|
||||
|
||||
WsLcdIcons Icons;
|
||||
|
||||
uint8_t LastScanline;
|
||||
uint8_t BackPorchScanline;
|
||||
|
||||
uint32_t ShowVolumeIconFrame;
|
||||
uint8_t LcdTftConfig[8];
|
||||
|
||||
uint8_t Control; //$00
|
||||
uint8_t ScreenAddress; //$07
|
||||
};
|
||||
|
||||
enum class WsSegment : uint8_t
|
||||
{
|
||||
Default,
|
||||
ES,
|
||||
SS,
|
||||
CS,
|
||||
DS
|
||||
};
|
||||
|
||||
enum class WsIrqSource : uint8_t
|
||||
{
|
||||
UartSendReady = 0x01,
|
||||
KeyPressed = 0x02,
|
||||
Cart = 0x04,
|
||||
UartRecvReady = 0x08,
|
||||
Scanline = 0x10,
|
||||
VerticalBlankTimer = 0x20,
|
||||
VerticalBlank = 0x40,
|
||||
HorizontalBlankTimer = 0x80
|
||||
};
|
||||
|
||||
struct WsMemoryManagerState
|
||||
{
|
||||
uint8_t ActiveIrqs;
|
||||
uint8_t EnabledIrqs;
|
||||
uint8_t IrqVectorOffset;
|
||||
|
||||
uint8_t SystemControl2;
|
||||
bool ColorEnabled;
|
||||
bool Enable4bpp;
|
||||
bool Enable4bppPacked;
|
||||
|
||||
bool BootRomDisabled;
|
||||
bool CartWordBus;
|
||||
bool SlowRom;
|
||||
|
||||
bool SlowSram;
|
||||
bool SlowPort;
|
||||
|
||||
bool EnableLowBatteryNmi;
|
||||
};
|
||||
|
||||
struct WsControlManagerState
|
||||
{
|
||||
uint8_t InputSelect;
|
||||
};
|
||||
|
||||
struct WsDmaControllerState
|
||||
{
|
||||
uint32_t GdmaSrc;
|
||||
uint32_t SdmaSrc;
|
||||
uint32_t SdmaLength;
|
||||
uint32_t SdmaSrcReloadValue;
|
||||
uint32_t SdmaLengthReloadValue;
|
||||
|
||||
uint16_t GdmaDest;
|
||||
uint16_t GdmaLength;
|
||||
uint8_t GdmaControl;
|
||||
uint8_t SdmaControl;
|
||||
|
||||
bool SdmaEnabled;
|
||||
bool SdmaDecrement;
|
||||
bool SdmaHyperVoice;
|
||||
bool SdmaRepeat;
|
||||
bool SdmaHold;
|
||||
uint8_t SdmaFrequency;
|
||||
uint8_t SdmaTimer;
|
||||
};
|
||||
|
||||
struct WsTimerState
|
||||
{
|
||||
uint16_t HTimer;
|
||||
uint16_t VTimer;
|
||||
|
||||
uint16_t HReloadValue;
|
||||
uint16_t VReloadValue;
|
||||
|
||||
uint8_t Control;
|
||||
bool HBlankEnabled;
|
||||
bool HBlankAutoReload;
|
||||
bool VBlankEnabled;
|
||||
bool VBlankAutoReload;
|
||||
};
|
||||
|
||||
struct BaseWsApuState
|
||||
{
|
||||
uint16_t Frequency;
|
||||
uint16_t Timer;
|
||||
|
||||
bool Enabled;
|
||||
uint8_t LeftVolume;
|
||||
uint8_t RightVolume;
|
||||
|
||||
uint8_t SamplePosition;
|
||||
uint8_t LeftOutput;
|
||||
uint8_t RightOutput;
|
||||
|
||||
void SetVolume(uint8_t value)
|
||||
{
|
||||
RightVolume = value & 0x0F;
|
||||
LeftVolume = (value >> 4);
|
||||
}
|
||||
|
||||
uint8_t GetVolume()
|
||||
{
|
||||
return RightVolume | (LeftVolume << 4);
|
||||
}
|
||||
};
|
||||
|
||||
struct WsApuCh1State : public BaseWsApuState {};
|
||||
|
||||
struct WsApuCh2State : public BaseWsApuState
|
||||
{
|
||||
bool PcmEnabled;
|
||||
bool MaxPcmVolumeRight;
|
||||
bool HalfPcmVolumeRight;
|
||||
bool MaxPcmVolumeLeft;
|
||||
bool HalfPcmVolumeLeft;
|
||||
};
|
||||
|
||||
struct WsApuCh3State : public BaseWsApuState
|
||||
{
|
||||
uint16_t SweepScaler;
|
||||
bool SweepEnabled;
|
||||
int8_t SweepValue;
|
||||
uint8_t SweepPeriod;
|
||||
uint8_t SweepTimer;
|
||||
bool UseSweepCpuClock;
|
||||
};
|
||||
|
||||
struct WsApuCh4State : public BaseWsApuState
|
||||
{
|
||||
bool NoiseEnabled;
|
||||
bool LfsrEnabled;
|
||||
uint8_t TapMode;
|
||||
uint8_t TapShift;
|
||||
uint16_t Lfsr;
|
||||
uint8_t HoldLfsr;
|
||||
};
|
||||
|
||||
enum class WsHyperVoiceScalingMode : uint8_t
|
||||
{
|
||||
Unsigned,
|
||||
UnsignedNegated,
|
||||
Signed,
|
||||
None
|
||||
};
|
||||
|
||||
enum class WsHyperVoiceChannelMode : uint8_t
|
||||
{
|
||||
Stereo,
|
||||
MonoLeft,
|
||||
MonoRight,
|
||||
MonoBoth
|
||||
};
|
||||
|
||||
struct WsApuHyperVoiceState
|
||||
{
|
||||
int16_t LeftOutput;
|
||||
int16_t RightOutput;
|
||||
|
||||
bool Enabled;
|
||||
|
||||
uint8_t LeftSample;
|
||||
uint8_t RightSample;
|
||||
bool UpdateRightValue;
|
||||
|
||||
uint8_t Divisor;
|
||||
uint8_t Timer;
|
||||
uint8_t Input;
|
||||
uint8_t Shift;
|
||||
WsHyperVoiceChannelMode ChannelMode;
|
||||
WsHyperVoiceScalingMode ScalingMode;
|
||||
|
||||
uint8_t ControlLow;
|
||||
uint8_t ControlHigh;
|
||||
};
|
||||
|
||||
struct WsApuState
|
||||
{
|
||||
WsApuCh1State Ch1;
|
||||
WsApuCh2State Ch2;
|
||||
WsApuCh3State Ch3;
|
||||
WsApuCh4State Ch4;
|
||||
WsApuHyperVoiceState Voice;
|
||||
|
||||
uint16_t WaveTableAddress;
|
||||
bool SpeakerEnabled;
|
||||
uint8_t SpeakerVolume;
|
||||
uint8_t InternalMasterVolume;
|
||||
uint8_t MasterVolume;
|
||||
bool HeadphoneEnabled;
|
||||
|
||||
bool HoldChannels;
|
||||
bool ForceOutput2;
|
||||
bool ForceOutput4;
|
||||
bool ForceOutputCh2Voice;
|
||||
};
|
||||
|
||||
struct WsSerialState
|
||||
{
|
||||
uint64_t SendClock;
|
||||
|
||||
bool Enabled;
|
||||
bool HighSpeed;
|
||||
bool ReceiveOverflow;
|
||||
|
||||
bool HasReceiveData;
|
||||
uint8_t ReceiveBuffer;
|
||||
|
||||
bool HasSendData;
|
||||
uint8_t SendBuffer;
|
||||
};
|
||||
|
||||
enum class WsEepromSize : uint16_t
|
||||
{
|
||||
Size0 = 0,
|
||||
Size128 = 0x80,
|
||||
Size1kb = 0x400,
|
||||
Size2kb = 0x800
|
||||
};
|
||||
|
||||
struct WsEepromState
|
||||
{
|
||||
WsEepromSize Size;
|
||||
uint16_t ReadBuffer;
|
||||
uint16_t WriteBuffer;
|
||||
uint16_t Command;
|
||||
uint16_t Control;
|
||||
bool WriteDisabled;
|
||||
bool ReadDone;
|
||||
bool Idle;
|
||||
|
||||
bool InternalEepromWriteProtected;
|
||||
};
|
||||
|
||||
struct WsCartState
|
||||
{
|
||||
uint8_t SelectedBanks[4];
|
||||
};
|
||||
|
||||
struct WsState
|
||||
{
|
||||
WsCpuState Cpu;
|
||||
WsPpuState Ppu;
|
||||
WsApuState Apu;
|
||||
WsMemoryManagerState MemoryManager;
|
||||
WsControlManagerState ControlManager;
|
||||
WsDmaControllerState DmaController;
|
||||
WsTimerState Timer;
|
||||
WsSerialState Serial;
|
||||
WsEepromState InternalEeprom;
|
||||
WsCartState Cart;
|
||||
WsEepromState CartEeprom;
|
||||
WsModel Model;
|
||||
};
|
||||
|
||||
class WsCpuParityTable
|
||||
{
|
||||
private:
|
||||
uint8_t _parityTable[0x100];
|
||||
|
||||
public:
|
||||
WsCpuParityTable()
|
||||
{
|
||||
for(int i = 0; i < 256; i++) {
|
||||
int count = 0;
|
||||
for(int j = 0; j < 8; j++) {
|
||||
count += (i & (1 << j)) ? 1 : 0;
|
||||
}
|
||||
_parityTable[i] = count & 0x01 ? 0 : 1;
|
||||
}
|
||||
}
|
||||
|
||||
__forceinline bool CheckParity(uint8_t val)
|
||||
{
|
||||
return _parityTable[val];
|
||||
}
|
||||
};
|
||||
@@ -66,6 +66,11 @@ extern "C" {
|
||||
_emu->GetSettings()->SetCvConfig(config);
|
||||
}
|
||||
|
||||
DllExport void __stdcall SetWsConfig(WsConfig config)
|
||||
{
|
||||
_emu->GetSettings()->SetWsConfig(config);
|
||||
}
|
||||
|
||||
DllExport void __stdcall SetGameConfig(GameConfig config)
|
||||
{
|
||||
_emu->GetSettings()->SetGameConfig(config);
|
||||
|
||||
@@ -34,7 +34,6 @@ SystemMouseState LinuxMouseManager::GetSystemMouseState(void* rendererHandle)
|
||||
|
||||
Window root = 0;
|
||||
Window c = 0;
|
||||
Window child = 0;
|
||||
int rootX, rootY, childX, childY;
|
||||
uint32_t mask;
|
||||
|
||||
@@ -42,7 +41,6 @@ SystemMouseState LinuxMouseManager::GetSystemMouseState(void* rendererHandle)
|
||||
XQueryPointer(_display, _rootWindow, &root, &c, &rootX, &rootY, &childX, &childY, &mask);
|
||||
if(root != _rootWindow) c = root;
|
||||
while(c != 0) {
|
||||
child = c;
|
||||
XQueryPointer(_display, c, &root, &c, &rootX, &rootY, &childX, &childY, &mask);
|
||||
}
|
||||
XUngrabServer(_display);
|
||||
|
||||
@@ -47,7 +47,7 @@ int main(int argc, char* argv[])
|
||||
romFolder = argv[1];
|
||||
}
|
||||
|
||||
vector<string> testRoms = GetFilesInFolder(romFolder, { ".sfc", ".gb", ".gbc", ".gbx", ".nes", ".pce", ".cue", ".sms", ".gg", ".sg", ".gba", ".col" });
|
||||
vector<string> testRoms = GetFilesInFolder(romFolder, { ".sfc", ".gb", ".gbc", ".gbx", ".nes", ".pce", ".cue", ".sms", ".gg", ".sg", ".gba", ".col", ".ws", ".wsc" });
|
||||
PgoRunTest(testRoms, true);
|
||||
return 0;
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 234 B |
@@ -33,6 +33,7 @@ namespace Mesen.Config
|
||||
[Reactive] public SmsConfig Sms { get; set; } = new();
|
||||
[Reactive] public CvConfig Cv { get; set; } = new();
|
||||
[Reactive] public GbaConfig Gba { get; set; } = new();
|
||||
[Reactive] public WsConfig Ws { get; set; } = new();
|
||||
[Reactive] public PreferencesConfig Preferences { get; set; } = new();
|
||||
[Reactive] public AudioPlayerConfig AudioPlayer { get; set; } = new();
|
||||
[Reactive] public DebugConfig Debug { get; set; } = new();
|
||||
@@ -88,6 +89,7 @@ namespace Mesen.Config
|
||||
Snes.ApplyConfig();
|
||||
Sms.ApplyConfig();
|
||||
Cv.ApplyConfig();
|
||||
Ws.ApplyConfig();
|
||||
Preferences.ApplyConfig();
|
||||
AudioPlayer.ApplyConfig();
|
||||
Debug.ApplyConfig();
|
||||
@@ -121,6 +123,10 @@ namespace Mesen.Config
|
||||
Cv.InitializeDefaults(DefaultKeyMappings);
|
||||
}
|
||||
|
||||
if(ConfigUpgrade < (int)ConfigUpgradeHint.WsInput) {
|
||||
Ws.InitializeDefaults(DefaultKeyMappings);
|
||||
}
|
||||
|
||||
ConfigUpgrade = (int)ConfigUpgradeHint.NextValue - 1;
|
||||
}
|
||||
|
||||
@@ -134,6 +140,7 @@ namespace Mesen.Config
|
||||
PcEngine.InitializeDefaults(DefaultKeyMappings);
|
||||
Sms.InitializeDefaults(DefaultKeyMappings);
|
||||
Cv.InitializeDefaults(DefaultKeyMappings);
|
||||
Ws.InitializeDefaults(DefaultKeyMappings);
|
||||
ConfigUpgrade = (int)ConfigUpgradeHint.NextValue - 1;
|
||||
}
|
||||
Preferences.InitializeDefaultShortcuts();
|
||||
@@ -305,6 +312,7 @@ namespace Mesen.Config
|
||||
SmsInput,
|
||||
GbaInput,
|
||||
CvInput,
|
||||
WsInput,
|
||||
NextValue,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ public class ConsoleOverrideConfig : BaseConfig<GameConfig>
|
||||
return ConfigManager.Config.Sms.ConfigOverrides;
|
||||
}
|
||||
case ConsoleType.Gba: return ConfigManager.Config.Gba.ConfigOverrides;
|
||||
case ConsoleType.Ws: return ConfigManager.Config.Ws.ConfigOverrides;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
+1
-36
@@ -54,42 +54,7 @@ public class CvConfig : BaseConfig<CvConfig>
|
||||
|
||||
internal void InitializeDefaults(DefaultKeyMappingType defaultMappings)
|
||||
{
|
||||
List<CvKeyMapping> mappings = new List<CvKeyMapping>();
|
||||
if(defaultMappings.HasFlag(DefaultKeyMappingType.Xbox)) {
|
||||
CvKeyMapping mapping = new();
|
||||
mapping.ColecoVisionControllerButtons = mapping.GetDefaultCustomKeys(ControllerType.ColecoVisionController, KeyPresetType.XboxP1);
|
||||
mappings.Add(mapping);
|
||||
}
|
||||
if(defaultMappings.HasFlag(DefaultKeyMappingType.Ps4)) {
|
||||
CvKeyMapping mapping = new();
|
||||
mapping.ColecoVisionControllerButtons = mapping.GetDefaultCustomKeys(ControllerType.ColecoVisionController, KeyPresetType.Ps4P1);
|
||||
mappings.Add(mapping);
|
||||
}
|
||||
if(defaultMappings.HasFlag(DefaultKeyMappingType.WasdKeys)) {
|
||||
CvKeyMapping mapping = new();
|
||||
mapping.ColecoVisionControllerButtons = mapping.GetDefaultCustomKeys(ControllerType.ColecoVisionController, KeyPresetType.WasdKeys);
|
||||
mappings.Add(mapping);
|
||||
}
|
||||
if(defaultMappings.HasFlag(DefaultKeyMappingType.ArrowKeys)) {
|
||||
CvKeyMapping mapping = new();
|
||||
mapping.ColecoVisionControllerButtons = mapping.GetDefaultCustomKeys(ControllerType.ColecoVisionController, KeyPresetType.ArrowKeys);
|
||||
mappings.Add(mapping);
|
||||
}
|
||||
|
||||
Port1.Type = ControllerType.ColecoVisionController;
|
||||
Port1.TurboSpeed = 2;
|
||||
if(mappings.Count > 0) {
|
||||
Port1.Mapping1 = mappings[0];
|
||||
if(mappings.Count > 1) {
|
||||
Port1.Mapping2 = mappings[1];
|
||||
if(mappings.Count > 2) {
|
||||
Port1.Mapping3 = mappings[2];
|
||||
if(mappings.Count > 3) {
|
||||
Port1.Mapping4 = mappings[3];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Port1.InitDefaults<CvKeyMapping>(defaultMappings, ControllerType.ColecoVisionController);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace Mesen.Config
|
||||
{
|
||||
UInt16[]? buttonMappings = GetCustomButtons(type);
|
||||
if(buttonMappings == null) {
|
||||
if(GetDefaultCustomKeys(type) != null) {
|
||||
if(GetDefaultCustomKeys(type, null) != null) {
|
||||
if(mappingIndex == 0) {
|
||||
SetDefaultKeys(type, null);
|
||||
} else {
|
||||
@@ -73,7 +73,7 @@ namespace Mesen.Config
|
||||
}
|
||||
}
|
||||
|
||||
public UInt16[]? GetDefaultCustomKeys(ControllerType type, KeyPresetType? preset)
|
||||
public override UInt16[]? GetDefaultCustomKeys(ControllerType type, KeyPresetType? preset)
|
||||
{
|
||||
switch(type) {
|
||||
case ControllerType.ColecoVisionController:
|
||||
@@ -129,11 +129,6 @@ namespace Mesen.Config
|
||||
}
|
||||
}
|
||||
|
||||
protected override UInt16[]? GetDefaultCustomKeys(ControllerType type)
|
||||
{
|
||||
return GetDefaultCustomKeys(type, null);
|
||||
}
|
||||
|
||||
public override void SetDefaultKeys(ControllerType type, KeyPresetType? preset)
|
||||
{
|
||||
switch(type) {
|
||||
|
||||
@@ -90,6 +90,8 @@ namespace Mesen.Config
|
||||
GbaBreakOnNopLoad = Debugger.Gba.BreakOnNopLoad,
|
||||
GbaBreakOnUnalignedMemAccess = Debugger.Gba.BreakOnUnalignedMemAccess,
|
||||
|
||||
WsBreakOnUndefinedOpCode = Debugger.Ws.BreakOnUndefinedOpCode,
|
||||
|
||||
ScriptAllowIoOsAccess = ScriptWindow.AllowIoOsAccess,
|
||||
ScriptAllowNetworkAccess = ScriptWindow.AllowNetworkAccess,
|
||||
ScriptTimeout = ScriptWindow.ScriptTimeout
|
||||
@@ -150,6 +152,8 @@ namespace Mesen.Config
|
||||
[MarshalAs(UnmanagedType.I1)] public bool GbaBreakOnNopLoad;
|
||||
[MarshalAs(UnmanagedType.I1)] public bool GbaBreakOnInvalidOpCode;
|
||||
[MarshalAs(UnmanagedType.I1)] public bool GbaBreakOnUnalignedMemAccess;
|
||||
|
||||
[MarshalAs(UnmanagedType.I1)] public bool WsBreakOnUndefinedOpCode;
|
||||
|
||||
[MarshalAs(UnmanagedType.I1)] public bool ScriptAllowIoOsAccess;
|
||||
[MarshalAs(UnmanagedType.I1)] public bool ScriptAllowNetworkAccess;
|
||||
|
||||
@@ -32,6 +32,7 @@ namespace Mesen.Config
|
||||
[Reactive] public GbaDebuggerConfig Gba { get; set; } = new();
|
||||
[Reactive] public PceDebuggerConfig Pce { get; set; } = new();
|
||||
[Reactive] public SmsDebuggerConfig Sms { get; set; } = new();
|
||||
[Reactive] public WsDebuggerConfig Ws { get; set; } = new();
|
||||
|
||||
[Reactive] public bool BreakOnUninitRead { get; set; } = false;
|
||||
[Reactive] public bool BreakOnOpen { get; set; } = true;
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
using Avalonia.Media;
|
||||
|
||||
namespace Mesen.Config;
|
||||
|
||||
public static class EventViewerColors
|
||||
{
|
||||
public static Color[] Colors => _colors;
|
||||
|
||||
private static readonly Color[] _colors = new Color[] {
|
||||
//Pairs
|
||||
Color.FromRgb(0x00, 0x6E, 0x8E),
|
||||
Color.FromRgb(0x78, 0xA5, 0xB2),
|
||||
|
||||
Color.FromRgb(0x97, 0x75, 0x00),
|
||||
Color.FromRgb(0xB7, 0xAA, 0x7B),
|
||||
|
||||
Color.FromRgb(0x2D, 0x92, 0x3D),
|
||||
Color.FromRgb(0x6A, 0xAF, 0x7B),
|
||||
|
||||
Color.FromRgb(0x96, 0x45, 0x45),
|
||||
Color.FromRgb(0xD3, 0x4F, 0x4F),
|
||||
|
||||
Color.FromRgb(0xE5, 0xCF, 0x2B),
|
||||
Color.FromRgb(0xFA, 0xFF, 0x39),
|
||||
|
||||
Color.FromRgb(0xD7, 0x2C, 0x2F),
|
||||
Color.FromRgb(0xFF, 0x16, 0x1A),
|
||||
|
||||
Color.FromRgb(0xF5, 0xFF, 0x72),
|
||||
Color.FromRgb(0xFD, 0xFF, 0xB3),
|
||||
|
||||
Color.FromRgb(0x41, 0xCE, 0xDB),
|
||||
Color.FromRgb(0xC9, 0xFF, 0xFD),
|
||||
|
||||
Color.FromRgb(0x9D, 0x6D, 0xC2),
|
||||
Color.FromRgb(0x84, 0x73, 0xC0),
|
||||
|
||||
Color.FromRgb(0x5D, 0xE3, 0x3C),
|
||||
Color.FromRgb(0xC4, 0xF4, 0x7A),
|
||||
|
||||
Color.FromRgb(0xC7, 0x80, 0x00),
|
||||
Color.FromRgb(0xFF, 0x8C, 0x00),
|
||||
|
||||
Color.FromRgb(0xBE, 0x52, 0xC0),
|
||||
Color.FromRgb(0xFC, 0x5E, 0xC6),
|
||||
|
||||
Color.FromRgb(0x00, 0xFF, 0x7F),
|
||||
Color.FromRgb(0x1A, 0xB0, 0x3D),
|
||||
|
||||
Color.FromRgb(0x9F, 0xCC, 0xA4),
|
||||
Color.FromRgb(0xC7, 0xFF, 0xC4),
|
||||
|
||||
//Single colors
|
||||
Color.FromRgb(0xFF, 0xA5, 0x62),
|
||||
Color.FromRgb(0x03, 0x35, 0xFF),
|
||||
Color.FromRgb(0x00, 0x8C, 0xBB),
|
||||
};
|
||||
}
|
||||
@@ -26,5 +26,6 @@ namespace Mesen.Config
|
||||
[Reactive] public GbaEventViewerConfig GbaConfig { get; set; } = new GbaEventViewerConfig();
|
||||
[Reactive] public PceEventViewerConfig PceConfig { get; set; } = new PceEventViewerConfig();
|
||||
[Reactive] public SmsEventViewerConfig SmsConfig { get; set; } = new SmsEventViewerConfig();
|
||||
[Reactive] public WsEventViewerConfig WsConfig { get; set; } = new WsEventViewerConfig();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ namespace Mesen.Config
|
||||
[Reactive] public TraceLoggerCpuConfig PceConfig { get; set; } = new();
|
||||
[Reactive] public TraceLoggerCpuConfig SmsConfig { get; set; } = new();
|
||||
[Reactive] public TraceLoggerCpuConfig GbaConfig { get; set; } = new();
|
||||
[Reactive] public TraceLoggerCpuConfig WsConfig { get; set; } = new();
|
||||
|
||||
public TraceLoggerConfig()
|
||||
{
|
||||
@@ -51,6 +52,7 @@ namespace Mesen.Config
|
||||
CpuType.Pce => PceConfig,
|
||||
CpuType.Sms => SmsConfig,
|
||||
CpuType.Gba => GbaConfig,
|
||||
CpuType.Ws => WsConfig,
|
||||
_ => throw new NotImplementedException("Unsupport cpu type")
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Media;
|
||||
using Mesen.Debugger;
|
||||
using Mesen.Interop;
|
||||
using ReactiveUI.Fody.Helpers;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive;
|
||||
using Mesen.ViewModels;
|
||||
|
||||
namespace Mesen.Config
|
||||
{
|
||||
public class WsDebuggerConfig : ViewModelBase
|
||||
{
|
||||
[Reactive] public bool BreakOnUndefinedOpCode { get; set; } = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
using Mesen.Interop;
|
||||
using Mesen.ViewModels;
|
||||
using ReactiveUI.Fody.Helpers;
|
||||
|
||||
namespace Mesen.Config;
|
||||
|
||||
public class WsEventViewerConfig : ViewModelBase
|
||||
{
|
||||
[Reactive] public EventViewerCategoryCfg PpuPaletteRead { get; set; } = new EventViewerCategoryCfg(EventViewerColors.Colors[0]);
|
||||
[Reactive] public EventViewerCategoryCfg PpuPaletteWrite { get; set; } = new EventViewerCategoryCfg(EventViewerColors.Colors[1]);
|
||||
|
||||
[Reactive] public EventViewerCategoryCfg PpuVramRead { get; set; } = new EventViewerCategoryCfg(EventViewerColors.Colors[2]);
|
||||
[Reactive] public EventViewerCategoryCfg PpuVramWrite { get; set; } = new EventViewerCategoryCfg(EventViewerColors.Colors[3]);
|
||||
|
||||
[Reactive] public EventViewerCategoryCfg PpuScrollRead { get; set; } = new EventViewerCategoryCfg(EventViewerColors.Colors[4]);
|
||||
[Reactive] public EventViewerCategoryCfg PpuScrollWrite { get; set; } = new EventViewerCategoryCfg(EventViewerColors.Colors[5]);
|
||||
[Reactive] public EventViewerCategoryCfg PpuWindowRead { get; set; } = new EventViewerCategoryCfg(EventViewerColors.Colors[6]);
|
||||
[Reactive] public EventViewerCategoryCfg PpuWindowWrite { get; set; } = new EventViewerCategoryCfg(EventViewerColors.Colors[7]);
|
||||
[Reactive] public EventViewerCategoryCfg PpuOtherRead { get; set; } = new EventViewerCategoryCfg(EventViewerColors.Colors[8]);
|
||||
[Reactive] public EventViewerCategoryCfg PpuOtherWrite { get; set; } = new EventViewerCategoryCfg(EventViewerColors.Colors[9]);
|
||||
|
||||
[Reactive] public EventViewerCategoryCfg AudioRead { get; set; } = new EventViewerCategoryCfg(EventViewerColors.Colors[10]);
|
||||
[Reactive] public EventViewerCategoryCfg AudioWrite { get; set; } = new EventViewerCategoryCfg(EventViewerColors.Colors[11]);
|
||||
|
||||
[Reactive] public EventViewerCategoryCfg SerialRead { get; set; } = new EventViewerCategoryCfg(EventViewerColors.Colors[12]);
|
||||
[Reactive] public EventViewerCategoryCfg SerialWrite { get; set; } = new EventViewerCategoryCfg(EventViewerColors.Colors[13]);
|
||||
|
||||
[Reactive] public EventViewerCategoryCfg DmaRead { get; set; } = new EventViewerCategoryCfg(EventViewerColors.Colors[14]);
|
||||
[Reactive] public EventViewerCategoryCfg DmaWrite { get; set; } = new EventViewerCategoryCfg(EventViewerColors.Colors[15]);
|
||||
|
||||
[Reactive] public EventViewerCategoryCfg InputRead { get; set; } = new EventViewerCategoryCfg(EventViewerColors.Colors[16]);
|
||||
[Reactive] public EventViewerCategoryCfg InputWrite { get; set; } = new EventViewerCategoryCfg(EventViewerColors.Colors[17]);
|
||||
|
||||
[Reactive] public EventViewerCategoryCfg IrqRead { get; set; } = new EventViewerCategoryCfg(EventViewerColors.Colors[18]);
|
||||
[Reactive] public EventViewerCategoryCfg IrqWrite { get; set; } = new EventViewerCategoryCfg(EventViewerColors.Colors[19]);
|
||||
|
||||
[Reactive] public EventViewerCategoryCfg TimerRead { get; set; } = new EventViewerCategoryCfg(EventViewerColors.Colors[20]);
|
||||
[Reactive] public EventViewerCategoryCfg TimerWrite { get; set; } = new EventViewerCategoryCfg(EventViewerColors.Colors[21]);
|
||||
|
||||
[Reactive] public EventViewerCategoryCfg EepromRead { get; set; } = new EventViewerCategoryCfg(EventViewerColors.Colors[22]);
|
||||
[Reactive] public EventViewerCategoryCfg EepromWrite { get; set; } = new EventViewerCategoryCfg(EventViewerColors.Colors[23]);
|
||||
|
||||
[Reactive] public EventViewerCategoryCfg CartRead { get; set; } = new EventViewerCategoryCfg(EventViewerColors.Colors[24]);
|
||||
[Reactive] public EventViewerCategoryCfg CartWrite { get; set; } = new EventViewerCategoryCfg(EventViewerColors.Colors[25]);
|
||||
|
||||
[Reactive] public EventViewerCategoryCfg OtherRead { get; set; } = new EventViewerCategoryCfg(EventViewerColors.Colors[26]);
|
||||
[Reactive] public EventViewerCategoryCfg OtherWrite { get; set; } = new EventViewerCategoryCfg(EventViewerColors.Colors[27]);
|
||||
|
||||
[Reactive] public EventViewerCategoryCfg PpuVCounterRead { get; set; } = new EventViewerCategoryCfg(EventViewerColors.Colors[28]);
|
||||
|
||||
[Reactive] public EventViewerCategoryCfg Irq { get; set; } = new EventViewerCategoryCfg(EventViewerColors.Colors[29]);
|
||||
|
||||
[Reactive] public EventViewerCategoryCfg MarkedBreakpoints { get; set; } = new EventViewerCategoryCfg(EventViewerColors.Colors[30]);
|
||||
|
||||
[Reactive] public bool ShowPreviousFrameEvents { get; set; } = true;
|
||||
|
||||
public InteropWsEventViewerConfig ToInterop()
|
||||
{
|
||||
return new InteropWsEventViewerConfig() {
|
||||
PpuPaletteRead = this.PpuPaletteRead,
|
||||
PpuPaletteWrite = this.PpuPaletteWrite,
|
||||
PpuVramRead = this.PpuVramRead,
|
||||
PpuVramWrite = this.PpuVramWrite,
|
||||
PpuVCounterRead = this.PpuVCounterRead,
|
||||
PpuScrollRead = this.PpuScrollRead,
|
||||
PpuScrollWrite = this.PpuScrollWrite,
|
||||
PpuWindowRead = this.PpuWindowRead,
|
||||
PpuWindowWrite = this.PpuWindowWrite,
|
||||
PpuOtherRead = this.PpuOtherRead,
|
||||
PpuOtherWrite = this.PpuOtherWrite,
|
||||
AudioRead = this.AudioRead,
|
||||
AudioWrite = this.AudioWrite,
|
||||
SerialRead = this.SerialRead,
|
||||
SerialWrite = this.SerialWrite,
|
||||
DmaRead = this.DmaRead,
|
||||
DmaWrite = this.DmaWrite,
|
||||
InputRead = this.InputRead,
|
||||
InputWrite = this.InputWrite,
|
||||
IrqRead = this.IrqRead,
|
||||
IrqWrite = this.IrqWrite,
|
||||
TimerRead = this.TimerRead,
|
||||
TimerWrite = this.TimerWrite,
|
||||
EepromRead = this.EepromRead,
|
||||
EepromWrite = this.EepromWrite,
|
||||
CartRead = this.CartRead,
|
||||
CartWrite = this.CartWrite,
|
||||
OtherRead = this.OtherRead,
|
||||
OtherWrite = this.OtherWrite,
|
||||
Irq = this.Irq,
|
||||
MarkedBreakpoints = this.MarkedBreakpoints,
|
||||
|
||||
ShowPreviousFrameEvents = this.ShowPreviousFrameEvents
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -90,6 +90,9 @@ namespace Mesen.Config
|
||||
CreateMimeType("x-mesen-gg", "gg", "Game Gear ROM", mimeTypes, cfg.AssociateGameGearRomFiles);
|
||||
CreateMimeType("x-mesen-sg", "sg", "SG-1000 ROM", mimeTypes, cfg.AssociateSgRomFiles);
|
||||
CreateMimeType("x-mesen-col", "col", "ColecoVision ROM", mimeTypes, cfg.AssociateCvRomFiles);
|
||||
|
||||
CreateMimeType("x-mesen-ws", "ws", "WonderSwan ROM", mimeTypes, cfg.AssociateWsRomFiles);
|
||||
CreateMimeType("x-mesen-wsc", "wsc", "WonderSwan Color ROM", mimeTypes, cfg.AssociateWsRomFiles);
|
||||
|
||||
//Icon used for shortcuts
|
||||
ImageUtilities.BitmapFromAsset("Assets/MesenIcon.png").Save(Path.Combine(iconFolder, "MesenIcon.png"));
|
||||
@@ -209,6 +212,9 @@ namespace Mesen.Config
|
||||
FileAssociationHelper.UpdateFileAssociation("gg", cfg.AssociateGameGearRomFiles);
|
||||
FileAssociationHelper.UpdateFileAssociation("sg", cfg.AssociateSgRomFiles);
|
||||
FileAssociationHelper.UpdateFileAssociation("col", cfg.AssociateCvRomFiles);
|
||||
|
||||
FileAssociationHelper.UpdateFileAssociation("ws", cfg.AssociateWsRomFiles);
|
||||
FileAssociationHelper.UpdateFileAssociation("wsc", cfg.AssociateWsRomFiles);
|
||||
}
|
||||
|
||||
static private void UpdateFileAssociation(string extension, bool associate)
|
||||
|
||||
@@ -68,42 +68,7 @@ namespace Mesen.Config
|
||||
|
||||
internal void InitializeDefaults(DefaultKeyMappingType defaultMappings)
|
||||
{
|
||||
List<KeyMapping> mappings = new List<KeyMapping>();
|
||||
if(defaultMappings.HasFlag(DefaultKeyMappingType.Xbox)) {
|
||||
KeyMapping mapping = new();
|
||||
KeyPresets.ApplyXboxLayout(mapping, 0, ControllerType.GameboyController);
|
||||
mappings.Add(mapping);
|
||||
}
|
||||
if(defaultMappings.HasFlag(DefaultKeyMappingType.Ps4)) {
|
||||
KeyMapping mapping = new();
|
||||
KeyPresets.ApplyPs4Layout(mapping, 0, ControllerType.GameboyController);
|
||||
mappings.Add(mapping);
|
||||
}
|
||||
if(defaultMappings.HasFlag(DefaultKeyMappingType.WasdKeys)) {
|
||||
KeyMapping mapping = new();
|
||||
KeyPresets.ApplyWasdLayout(mapping, ControllerType.GameboyController);
|
||||
mappings.Add(mapping);
|
||||
}
|
||||
if(defaultMappings.HasFlag(DefaultKeyMappingType.ArrowKeys)) {
|
||||
KeyMapping mapping = new();
|
||||
KeyPresets.ApplyArrowLayout(mapping, ControllerType.GameboyController);
|
||||
mappings.Add(mapping);
|
||||
}
|
||||
|
||||
Controller.Type = ControllerType.GameboyController;
|
||||
Controller.TurboSpeed = 2;
|
||||
if(mappings.Count > 0) {
|
||||
Controller.Mapping1 = mappings[0];
|
||||
if(mappings.Count > 1) {
|
||||
Controller.Mapping2 = mappings[1];
|
||||
if(mappings.Count > 2) {
|
||||
Controller.Mapping3 = mappings[2];
|
||||
if(mappings.Count > 3) {
|
||||
Controller.Mapping4 = mappings[3];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Controller.InitDefaults(defaultMappings, ControllerType.GameboyController);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-36
@@ -71,42 +71,7 @@ namespace Mesen.Config
|
||||
|
||||
internal void InitializeDefaults(DefaultKeyMappingType defaultMappings)
|
||||
{
|
||||
List<KeyMapping> mappings = new List<KeyMapping>();
|
||||
if(defaultMappings.HasFlag(DefaultKeyMappingType.Xbox)) {
|
||||
KeyMapping mapping = new();
|
||||
KeyPresets.ApplyXboxLayout(mapping, 0, ControllerType.GbaController);
|
||||
mappings.Add(mapping);
|
||||
}
|
||||
if(defaultMappings.HasFlag(DefaultKeyMappingType.Ps4)) {
|
||||
KeyMapping mapping = new();
|
||||
KeyPresets.ApplyPs4Layout(mapping, 0, ControllerType.GbaController);
|
||||
mappings.Add(mapping);
|
||||
}
|
||||
if(defaultMappings.HasFlag(DefaultKeyMappingType.WasdKeys)) {
|
||||
KeyMapping mapping = new();
|
||||
KeyPresets.ApplyWasdLayout(mapping, ControllerType.GbaController);
|
||||
mappings.Add(mapping);
|
||||
}
|
||||
if(defaultMappings.HasFlag(DefaultKeyMappingType.ArrowKeys)) {
|
||||
KeyMapping mapping = new();
|
||||
KeyPresets.ApplyArrowLayout(mapping, ControllerType.GbaController);
|
||||
mappings.Add(mapping);
|
||||
}
|
||||
|
||||
Controller.Type = ControllerType.GbaController;
|
||||
Controller.TurboSpeed = 2;
|
||||
if(mappings.Count > 0) {
|
||||
Controller.Mapping1 = mappings[0];
|
||||
if(mappings.Count > 1) {
|
||||
Controller.Mapping2 = mappings[1];
|
||||
if(mappings.Count > 2) {
|
||||
Controller.Mapping3 = mappings[2];
|
||||
if(mappings.Count > 3) {
|
||||
Controller.Mapping4 = mappings[3];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Controller.InitDefaults(defaultMappings, ControllerType.GbaController);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user