Core: Add Multitap support
This commit is contained in:
parent
bd9cb67565
commit
f9dc1a7e80
|
@ -67,6 +67,8 @@ add_library(core
|
||||||
memory_card.h
|
memory_card.h
|
||||||
memory_card_image.cpp
|
memory_card_image.cpp
|
||||||
memory_card_image.h
|
memory_card_image.h
|
||||||
|
multitap.cpp
|
||||||
|
multitap.h
|
||||||
namco_guncon.cpp
|
namco_guncon.cpp
|
||||||
namco_guncon.h
|
namco_guncon.h
|
||||||
negcon.cpp
|
negcon.cpp
|
||||||
|
|
|
@ -136,6 +136,7 @@
|
||||||
<ClCompile Include="mdec.cpp" />
|
<ClCompile Include="mdec.cpp" />
|
||||||
<ClCompile Include="memory_card.cpp" />
|
<ClCompile Include="memory_card.cpp" />
|
||||||
<ClCompile Include="memory_card_image.cpp" />
|
<ClCompile Include="memory_card_image.cpp" />
|
||||||
|
<ClCompile Include="multitap.cpp" />
|
||||||
<ClCompile Include="namco_guncon.cpp" />
|
<ClCompile Include="namco_guncon.cpp" />
|
||||||
<ClCompile Include="negcon.cpp" />
|
<ClCompile Include="negcon.cpp" />
|
||||||
<ClCompile Include="pad.cpp" />
|
<ClCompile Include="pad.cpp" />
|
||||||
|
@ -213,6 +214,7 @@
|
||||||
<ClInclude Include="mdec.h" />
|
<ClInclude Include="mdec.h" />
|
||||||
<ClInclude Include="memory_card.h" />
|
<ClInclude Include="memory_card.h" />
|
||||||
<ClInclude Include="memory_card_image.h" />
|
<ClInclude Include="memory_card_image.h" />
|
||||||
|
<ClInclude Include="multitap.h" />
|
||||||
<ClInclude Include="namco_guncon.h" />
|
<ClInclude Include="namco_guncon.h" />
|
||||||
<ClInclude Include="negcon.h" />
|
<ClInclude Include="negcon.h" />
|
||||||
<ClInclude Include="pad.h" />
|
<ClInclude Include="pad.h" />
|
||||||
|
|
|
@ -57,6 +57,7 @@
|
||||||
<ClCompile Include="libcrypt_game_codes.cpp" />
|
<ClCompile Include="libcrypt_game_codes.cpp" />
|
||||||
<ClCompile Include="texture_replacements.cpp" />
|
<ClCompile Include="texture_replacements.cpp" />
|
||||||
<ClCompile Include="gdb_protocol.h" />
|
<ClCompile Include="gdb_protocol.h" />
|
||||||
|
<ClCompile Include="multitap.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="types.h" />
|
<ClInclude Include="types.h" />
|
||||||
|
@ -116,5 +117,6 @@
|
||||||
<ClInclude Include="libcrypt_game_codes.h" />
|
<ClInclude Include="libcrypt_game_codes.h" />
|
||||||
<ClInclude Include="texture_replacements.h" />
|
<ClInclude Include="texture_replacements.h" />
|
||||||
<ClInclude Include="shader_cache_version.h" />
|
<ClInclude Include="shader_cache_version.h" />
|
||||||
|
<ClInclude Include="multitap.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
|
@ -580,7 +580,12 @@ void HostInterface::SetDefaultSettings(SettingsInterface& si)
|
||||||
si.SetBoolValue("BIOS", "PatchFastBoot", false);
|
si.SetBoolValue("BIOS", "PatchFastBoot", false);
|
||||||
|
|
||||||
si.SetStringValue("Controller1", "Type", Settings::GetControllerTypeName(Settings::DEFAULT_CONTROLLER_1_TYPE));
|
si.SetStringValue("Controller1", "Type", Settings::GetControllerTypeName(Settings::DEFAULT_CONTROLLER_1_TYPE));
|
||||||
si.SetStringValue("Controller2", "Type", Settings::GetControllerTypeName(Settings::DEFAULT_CONTROLLER_2_TYPE));
|
|
||||||
|
for (u32 i = 1; i < NUM_CONTROLLER_AND_CARD_PORTS; i++)
|
||||||
|
{
|
||||||
|
si.SetStringValue(TinyString::FromFormat("Controller%u", i + 1u), "Type",
|
||||||
|
Settings::GetControllerTypeName(Settings::DEFAULT_CONTROLLER_2_TYPE));
|
||||||
|
}
|
||||||
|
|
||||||
si.SetStringValue("MemoryCards", "Card1Type", Settings::GetMemoryCardTypeName(Settings::DEFAULT_MEMORY_CARD_1_TYPE));
|
si.SetStringValue("MemoryCards", "Card1Type", Settings::GetMemoryCardTypeName(Settings::DEFAULT_MEMORY_CARD_1_TYPE));
|
||||||
si.SetStringValue("MemoryCards", "Card1Path", "memcards" FS_OSPATH_SEPARATOR_STR "shared_card_1.mcd");
|
si.SetStringValue("MemoryCards", "Card1Path", "memcards" FS_OSPATH_SEPARATOR_STR "shared_card_1.mcd");
|
||||||
|
@ -588,6 +593,9 @@ void HostInterface::SetDefaultSettings(SettingsInterface& si)
|
||||||
si.SetStringValue("MemoryCards", "Card2Path", "memcards" FS_OSPATH_SEPARATOR_STR "shared_card_2.mcd");
|
si.SetStringValue("MemoryCards", "Card2Path", "memcards" FS_OSPATH_SEPARATOR_STR "shared_card_2.mcd");
|
||||||
si.SetBoolValue("MemoryCards", "UsePlaylistTitle", true);
|
si.SetBoolValue("MemoryCards", "UsePlaylistTitle", true);
|
||||||
|
|
||||||
|
si.SetStringValue("ControllerPorts", "MultitapMode",
|
||||||
|
Settings::GetMultitapModeName(Settings::DEFAULT_MULTITAP_MODE));
|
||||||
|
|
||||||
si.SetStringValue("Logging", "LogLevel", Settings::GetLogLevelName(Settings::DEFAULT_LOG_LEVEL));
|
si.SetStringValue("Logging", "LogLevel", Settings::GetLogLevelName(Settings::DEFAULT_LOG_LEVEL));
|
||||||
si.SetStringValue("Logging", "LogFilter", "");
|
si.SetStringValue("Logging", "LogFilter", "");
|
||||||
si.SetBoolValue("Logging", "LogToConsole", Settings::DEFAULT_LOG_TO_CONSOLE);
|
si.SetBoolValue("Logging", "LogToConsole", Settings::DEFAULT_LOG_TO_CONSOLE);
|
||||||
|
@ -873,6 +881,9 @@ void HostInterface::CheckForSettingsChanges(const Settings& old_settings)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (g_settings.multitap_mode != old_settings.multitap_mode)
|
||||||
|
System::UpdateMultitaps();
|
||||||
|
|
||||||
if (m_display && g_settings.display_linear_filtering != old_settings.display_linear_filtering)
|
if (m_display && g_settings.display_linear_filtering != old_settings.display_linear_filtering)
|
||||||
m_display->SetDisplayLinearFiltering(g_settings.display_linear_filtering);
|
m_display->SetDisplayLinearFiltering(g_settings.display_linear_filtering);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,244 @@
|
||||||
|
#include "multitap.h"
|
||||||
|
#include "common/log.h"
|
||||||
|
#include "common/state_wrapper.h"
|
||||||
|
#include "common/types.h"
|
||||||
|
#include "controller.h"
|
||||||
|
#include "memory_card.h"
|
||||||
|
#include "pad.h"
|
||||||
|
Log_SetChannel(Multitap);
|
||||||
|
|
||||||
|
Multitap::Multitap(u32 index) : m_index(index)
|
||||||
|
{
|
||||||
|
m_index = index;
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Multitap::Reset()
|
||||||
|
{
|
||||||
|
m_transfer_state = TransferState::Idle;
|
||||||
|
m_selected_slot = 0;
|
||||||
|
m_controller_transfer_step = 0;
|
||||||
|
m_transfer_all_controllers = false;
|
||||||
|
m_invalid_transfer_all_command = false;
|
||||||
|
m_current_controller_done = false;
|
||||||
|
m_transfer_buffer.fill(0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Multitap::DoState(StateWrapper& sw)
|
||||||
|
{
|
||||||
|
sw.Do(&m_transfer_state);
|
||||||
|
sw.Do(&m_selected_slot);
|
||||||
|
sw.Do(&m_controller_transfer_step);
|
||||||
|
sw.Do(&m_invalid_transfer_all_command);
|
||||||
|
sw.Do(&m_transfer_all_controllers);
|
||||||
|
sw.Do(&m_current_controller_done);
|
||||||
|
sw.Do(&m_transfer_buffer);
|
||||||
|
|
||||||
|
return !sw.HasError();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Multitap::ResetTransferState()
|
||||||
|
{
|
||||||
|
m_transfer_state = TransferState::Idle;
|
||||||
|
m_selected_slot = 0;
|
||||||
|
m_controller_transfer_step = 0;
|
||||||
|
m_current_controller_done = false;
|
||||||
|
|
||||||
|
// Don't reset m_transfer_all_controllers here, since it's queued up for the next transfer sequence
|
||||||
|
// Controller and memory card transfer resets are handled in the Pad class
|
||||||
|
}
|
||||||
|
|
||||||
|
Controller* Multitap::GetControllerForSlot(u32 slot) const
|
||||||
|
{
|
||||||
|
return g_pad.GetController(m_index * 4 + slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
MemoryCard* Multitap::GetMemoryCardForSlot(u32 slot) const
|
||||||
|
{
|
||||||
|
return g_pad.GetMemoryCard(m_index * 4 + slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Multitap::TransferController(u32 slot, const u8 data_in, u8* data_out) const
|
||||||
|
{
|
||||||
|
Controller* const selected_controller = GetControllerForSlot(slot);
|
||||||
|
if (!selected_controller)
|
||||||
|
{
|
||||||
|
*data_out = 0xFF;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return selected_controller->Transfer(data_in, data_out);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Multitap::TransferMemoryCard(u32 slot, const u8 data_in, u8* data_out) const
|
||||||
|
{
|
||||||
|
MemoryCard* const selected_memcard = GetMemoryCardForSlot(slot);
|
||||||
|
if (!selected_memcard)
|
||||||
|
{
|
||||||
|
*data_out = 0xFF;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return selected_memcard->Transfer(data_in, data_out);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Multitap::Transfer(const u8 data_in, u8* data_out)
|
||||||
|
{
|
||||||
|
bool ack = false;
|
||||||
|
switch (m_transfer_state)
|
||||||
|
{
|
||||||
|
case TransferState::Idle:
|
||||||
|
{
|
||||||
|
switch (data_in)
|
||||||
|
{
|
||||||
|
case 0x81:
|
||||||
|
case 0x82:
|
||||||
|
case 0x83:
|
||||||
|
case 0x84:
|
||||||
|
{
|
||||||
|
m_selected_slot = (data_in & 0x0F) - 1u;
|
||||||
|
ack = TransferMemoryCard(m_selected_slot, 0x81, data_out);
|
||||||
|
|
||||||
|
if (ack)
|
||||||
|
m_transfer_state = TransferState::MemoryCard;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x01:
|
||||||
|
case 0x02:
|
||||||
|
case 0x03:
|
||||||
|
case 0x04:
|
||||||
|
{
|
||||||
|
m_selected_slot = data_in - 1u;
|
||||||
|
ack = TransferController(m_selected_slot, 0x01, data_out);
|
||||||
|
|
||||||
|
if (ack)
|
||||||
|
{
|
||||||
|
m_transfer_state = TransferState::ControllerCommand;
|
||||||
|
|
||||||
|
if (m_transfer_all_controllers)
|
||||||
|
{
|
||||||
|
// Send access byte to remaining controllers for this transfer mode
|
||||||
|
u8 dummy_value;
|
||||||
|
for (u32 i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
if (i != m_selected_slot)
|
||||||
|
TransferController(i, 0x01, &dummy_value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
*data_out = 0xFF;
|
||||||
|
ack = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TransferState::MemoryCard:
|
||||||
|
{
|
||||||
|
ack = TransferMemoryCard(m_selected_slot, data_in, data_out);
|
||||||
|
|
||||||
|
if (!ack)
|
||||||
|
{
|
||||||
|
Log_DevPrintf("Memory card transfer ended");
|
||||||
|
m_transfer_state = TransferState::Idle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TransferState::ControllerCommand:
|
||||||
|
{
|
||||||
|
if (m_controller_transfer_step == 0) // Command byte
|
||||||
|
{
|
||||||
|
if (m_transfer_all_controllers)
|
||||||
|
{
|
||||||
|
// Unknown if 0x42 is the only valid command byte here, but other tested command bytes cause early aborts
|
||||||
|
*data_out = GetMultitapIDByte();
|
||||||
|
m_invalid_transfer_all_command = (data_in != 0x42);
|
||||||
|
ack = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ack = TransferController(m_selected_slot, data_in, data_out);
|
||||||
|
}
|
||||||
|
m_controller_transfer_step++;
|
||||||
|
}
|
||||||
|
else if (m_controller_transfer_step == 1) // Request byte
|
||||||
|
{
|
||||||
|
if (m_transfer_all_controllers)
|
||||||
|
{
|
||||||
|
*data_out = GetStatusByte();
|
||||||
|
|
||||||
|
ack = !m_invalid_transfer_all_command;
|
||||||
|
m_selected_slot = 0;
|
||||||
|
m_transfer_state = TransferState::AllControllers;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ack = TransferController(m_selected_slot, 0x00, data_out);
|
||||||
|
m_transfer_state = TransferState::SingleController;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Queue up request for next transfer cycle (not sure if this is always queued on invalid commands)
|
||||||
|
m_transfer_all_controllers = (data_in & 0x01);
|
||||||
|
m_controller_transfer_step = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UnreachableCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TransferState::SingleController:
|
||||||
|
{
|
||||||
|
// TODO: Check if the transfer buffer get wiped when transitioning to/from this mode
|
||||||
|
|
||||||
|
ack = TransferController(m_selected_slot, data_in, data_out);
|
||||||
|
|
||||||
|
if (!ack)
|
||||||
|
{
|
||||||
|
Log_DevPrintf("Controller transfer ended");
|
||||||
|
m_transfer_state = TransferState::Idle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TransferState::AllControllers:
|
||||||
|
{
|
||||||
|
// In this mode, we transfer until reaching 8 bytes or the controller finishes its response (no ack is returned).
|
||||||
|
// The hardware is probably either latching the controller info halfword count or waiting for a transfer timeout
|
||||||
|
// (timeouts might be possible due to buffered responses in this mode, and if the controllers are transferred in
|
||||||
|
// parallel rather than sequentially like we're doing here). We'll just simplify this and check the ack return
|
||||||
|
// value since our controller implementations are deterministic.
|
||||||
|
|
||||||
|
*data_out = m_transfer_buffer[m_controller_transfer_step];
|
||||||
|
ack = true;
|
||||||
|
|
||||||
|
if (m_current_controller_done)
|
||||||
|
m_transfer_buffer[m_controller_transfer_step] = 0xFF;
|
||||||
|
else
|
||||||
|
m_current_controller_done =
|
||||||
|
!TransferController(m_selected_slot, data_in, &m_transfer_buffer[m_controller_transfer_step]);
|
||||||
|
|
||||||
|
m_controller_transfer_step++;
|
||||||
|
if (m_controller_transfer_step % 8 == 0)
|
||||||
|
{
|
||||||
|
m_current_controller_done = false;
|
||||||
|
m_selected_slot = (m_selected_slot + 1) % 4;
|
||||||
|
if (m_selected_slot == 0)
|
||||||
|
ack = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
DefaultCaseIsUnreachable();
|
||||||
|
}
|
||||||
|
return ack;
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
#pragma once
|
||||||
|
#include "common/state_wrapper.h"
|
||||||
|
#include "common/types.h"
|
||||||
|
#include "controller.h"
|
||||||
|
#include "memory_card.h"
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
class Multitap final
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Multitap(u32 index);
|
||||||
|
|
||||||
|
void Reset();
|
||||||
|
|
||||||
|
ALWAYS_INLINE void SetEnable(bool enable) { m_enabled = enable; };
|
||||||
|
ALWAYS_INLINE bool IsEnabled() const { return m_enabled; };
|
||||||
|
|
||||||
|
bool DoState(StateWrapper& sw);
|
||||||
|
|
||||||
|
void ResetTransferState();
|
||||||
|
bool Transfer(const u8 data_in, u8* data_out);
|
||||||
|
ALWAYS_INLINE bool IsReadingMemoryCard() { return m_enabled && m_transfer_state == TransferState::MemoryCard; };
|
||||||
|
|
||||||
|
private:
|
||||||
|
ALWAYS_INLINE static constexpr u8 GetMultitapIDByte() { return 0x80; };
|
||||||
|
ALWAYS_INLINE static constexpr u8 GetStatusByte() { return 0x5A; };
|
||||||
|
|
||||||
|
Controller* GetControllerForSlot(u32 slot) const;
|
||||||
|
MemoryCard* GetMemoryCardForSlot(u32 slot) const;
|
||||||
|
|
||||||
|
bool TransferController(u32 slot, const u8 data_in, u8* data_out) const;
|
||||||
|
bool TransferMemoryCard(u32 slot, const u8 data_in, u8* data_out) const;
|
||||||
|
|
||||||
|
enum class TransferState : u8
|
||||||
|
{
|
||||||
|
Idle,
|
||||||
|
MemoryCard,
|
||||||
|
ControllerCommand,
|
||||||
|
SingleController,
|
||||||
|
AllControllers
|
||||||
|
};
|
||||||
|
|
||||||
|
TransferState m_transfer_state = TransferState::Idle;
|
||||||
|
u8 m_selected_slot = 0;
|
||||||
|
|
||||||
|
u32 m_controller_transfer_step = 0;
|
||||||
|
|
||||||
|
bool m_invalid_transfer_all_command = false;
|
||||||
|
bool m_transfer_all_controllers = false;
|
||||||
|
bool m_current_controller_done = false;
|
||||||
|
|
||||||
|
std::array<u8, 32> m_transfer_buffer{};
|
||||||
|
|
||||||
|
u32 m_index;
|
||||||
|
bool m_enabled = false;
|
||||||
|
};
|
|
@ -5,6 +5,7 @@
|
||||||
#include "host_interface.h"
|
#include "host_interface.h"
|
||||||
#include "interrupt_controller.h"
|
#include "interrupt_controller.h"
|
||||||
#include "memory_card.h"
|
#include "memory_card.h"
|
||||||
|
#include "multitap.h"
|
||||||
#include "system.h"
|
#include "system.h"
|
||||||
Log_SetChannel(Pad);
|
Log_SetChannel(Pad);
|
||||||
|
|
||||||
|
@ -27,7 +28,7 @@ void Pad::Shutdown()
|
||||||
{
|
{
|
||||||
m_transfer_event.reset();
|
m_transfer_event.reset();
|
||||||
|
|
||||||
for (u32 i = 0; i < NUM_SLOTS; i++)
|
for (u32 i = 0; i < NUM_CONTROLLER_AND_CARD_PORTS; i++)
|
||||||
{
|
{
|
||||||
m_controllers[i].reset();
|
m_controllers[i].reset();
|
||||||
m_memory_cards[i].reset();
|
m_memory_cards[i].reset();
|
||||||
|
@ -38,7 +39,7 @@ void Pad::Reset()
|
||||||
{
|
{
|
||||||
SoftReset();
|
SoftReset();
|
||||||
|
|
||||||
for (u32 i = 0; i < NUM_SLOTS; i++)
|
for (u32 i = 0; i < NUM_CONTROLLER_AND_CARD_PORTS; i++)
|
||||||
{
|
{
|
||||||
if (m_controllers[i])
|
if (m_controllers[i])
|
||||||
m_controllers[i]->Reset();
|
m_controllers[i]->Reset();
|
||||||
|
@ -46,12 +47,18 @@ void Pad::Reset()
|
||||||
if (m_memory_cards[i])
|
if (m_memory_cards[i])
|
||||||
m_memory_cards[i]->Reset();
|
m_memory_cards[i]->Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (u32 i = 0; i < NUM_MULTITAPS; i++)
|
||||||
|
m_multitaps[i].Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Pad::DoState(StateWrapper& sw)
|
bool Pad::DoState(StateWrapper& sw)
|
||||||
{
|
{
|
||||||
for (u32 i = 0; i < NUM_SLOTS; i++)
|
for (u32 i = 0; i < NUM_CONTROLLER_AND_CARD_PORTS; i++)
|
||||||
{
|
{
|
||||||
|
if (i > 1 && sw.GetVersion() < 50)
|
||||||
|
continue;
|
||||||
|
|
||||||
ControllerType controller_type = m_controllers[i] ? m_controllers[i]->GetType() : ControllerType::None;
|
ControllerType controller_type = m_controllers[i] ? m_controllers[i]->GetType() : ControllerType::None;
|
||||||
ControllerType state_controller_type = controller_type;
|
ControllerType state_controller_type = controller_type;
|
||||||
sw.Do(&state_controller_type);
|
sw.Do(&state_controller_type);
|
||||||
|
@ -205,6 +212,15 @@ bool Pad::DoState(StateWrapper& sw)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sw.GetVersion() > 49)
|
||||||
|
{
|
||||||
|
for (u32 i = 0; i < NUM_MULTITAPS; i++)
|
||||||
|
{
|
||||||
|
if (!m_multitaps[i].DoState(sw))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sw.Do(&m_state);
|
sw.Do(&m_state);
|
||||||
sw.Do(&m_JOY_CTRL.bits);
|
sw.Do(&m_JOY_CTRL.bits);
|
||||||
sw.Do(&m_JOY_STAT.bits);
|
sw.Do(&m_JOY_STAT.bits);
|
||||||
|
@ -231,6 +247,15 @@ void Pad::SetMemoryCard(u32 slot, std::unique_ptr<MemoryCard> dev)
|
||||||
m_memory_cards[slot] = std::move(dev);
|
m_memory_cards[slot] = std::move(dev);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Pad::SetMultitapEnable(u32 port, bool enable)
|
||||||
|
{
|
||||||
|
if (m_multitaps[port].IsEnabled() != enable)
|
||||||
|
{
|
||||||
|
m_multitaps[port].SetEnable(enable);
|
||||||
|
m_multitaps[port].Reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
u32 Pad::ReadRegister(u32 offset)
|
u32 Pad::ReadRegister(u32 offset)
|
||||||
{
|
{
|
||||||
switch (offset)
|
switch (offset)
|
||||||
|
@ -409,8 +434,9 @@ void Pad::DoTransfer(TickCount ticks_late)
|
||||||
{
|
{
|
||||||
Log_DebugPrintf("Transferring slot %d", m_JOY_CTRL.SLOT.GetValue());
|
Log_DebugPrintf("Transferring slot %d", m_JOY_CTRL.SLOT.GetValue());
|
||||||
|
|
||||||
Controller* const controller = m_controllers[m_JOY_CTRL.SLOT].get();
|
const u8 device_index = m_multitaps[0].IsEnabled() ? 4u : m_JOY_CTRL.SLOT;
|
||||||
MemoryCard* const memory_card = m_memory_cards[m_JOY_CTRL.SLOT].get();
|
Controller* const controller = m_controllers[device_index].get();
|
||||||
|
MemoryCard* const memory_card = m_memory_cards[device_index].get();
|
||||||
|
|
||||||
// set rx?
|
// set rx?
|
||||||
m_JOY_CTRL.RXEN = true;
|
m_JOY_CTRL.RXEN = true;
|
||||||
|
@ -423,6 +449,17 @@ void Pad::DoTransfer(TickCount ticks_late)
|
||||||
switch (m_active_device)
|
switch (m_active_device)
|
||||||
{
|
{
|
||||||
case ActiveDevice::None:
|
case ActiveDevice::None:
|
||||||
|
{
|
||||||
|
if (m_multitaps[m_JOY_CTRL.SLOT].IsEnabled())
|
||||||
|
{
|
||||||
|
if ((ack = m_multitaps[m_JOY_CTRL.SLOT].Transfer(data_out, &data_in)) == true)
|
||||||
|
{
|
||||||
|
Log_TracePrintf("Active device set to tap %d, sent 0x%02X, received 0x%02X",
|
||||||
|
static_cast<int>(m_JOY_CTRL.SLOT), data_out, data_in);
|
||||||
|
m_active_device = ActiveDevice::Multitap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
if (!controller || (ack = controller->Transfer(data_out, &data_in)) == false)
|
if (!controller || (ack = controller->Transfer(data_out, &data_in)) == false)
|
||||||
{
|
{
|
||||||
|
@ -445,6 +482,7 @@ void Pad::DoTransfer(TickCount ticks_late)
|
||||||
m_active_device = ActiveDevice::Controller;
|
m_active_device = ActiveDevice::Controller;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ActiveDevice::Controller:
|
case ActiveDevice::Controller:
|
||||||
|
@ -466,6 +504,17 @@ void Pad::DoTransfer(TickCount ticks_late)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case ActiveDevice::Multitap:
|
||||||
|
{
|
||||||
|
if (m_multitaps[m_JOY_CTRL.SLOT].IsEnabled())
|
||||||
|
{
|
||||||
|
ack = m_multitaps[m_JOY_CTRL.SLOT].Transfer(data_out, &data_in);
|
||||||
|
Log_TracePrintf("Transfer tap %d, sent 0x%02X, received 0x%02X, acked: %s", static_cast<int>(m_JOY_CTRL.SLOT),
|
||||||
|
data_out, data_in, ack ? "true" : "false");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_receive_buffer = data_in;
|
m_receive_buffer = data_in;
|
||||||
|
@ -479,7 +528,11 @@ void Pad::DoTransfer(TickCount ticks_late)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
const TickCount ack_timer = GetACKTicks(m_active_device == ActiveDevice::MemoryCard);
|
const bool memcard_transfer =
|
||||||
|
m_active_device == ActiveDevice::MemoryCard ||
|
||||||
|
(m_active_device == ActiveDevice::Multitap && m_multitaps[m_JOY_CTRL.SLOT].IsReadingMemoryCard());
|
||||||
|
|
||||||
|
const TickCount ack_timer = GetACKTicks(memcard_transfer);
|
||||||
Log_DebugPrintf("Delaying ACK for %d ticks", ack_timer);
|
Log_DebugPrintf("Delaying ACK for %d ticks", ack_timer);
|
||||||
m_state = State::WaitingForACK;
|
m_state = State::WaitingForACK;
|
||||||
m_transfer_event->SetPeriodAndSchedule(ack_timer);
|
m_transfer_event->SetPeriodAndSchedule(ack_timer);
|
||||||
|
@ -517,13 +570,16 @@ void Pad::EndTransfer()
|
||||||
|
|
||||||
void Pad::ResetDeviceTransferState()
|
void Pad::ResetDeviceTransferState()
|
||||||
{
|
{
|
||||||
for (u32 i = 0; i < NUM_SLOTS; i++)
|
for (u32 i = 0; i < NUM_CONTROLLER_AND_CARD_PORTS; i++)
|
||||||
{
|
{
|
||||||
if (m_controllers[i])
|
if (m_controllers[i])
|
||||||
m_controllers[i]->ResetTransferState();
|
m_controllers[i]->ResetTransferState();
|
||||||
if (m_memory_cards[i])
|
if (m_memory_cards[i])
|
||||||
m_memory_cards[i]->ResetTransferState();
|
m_memory_cards[i]->ResetTransferState();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (u32 i = 0; i < NUM_MULTITAPS; i++)
|
||||||
|
m_multitaps[i].ResetTransferState();
|
||||||
|
|
||||||
m_active_device = ActiveDevice::None;
|
m_active_device = ActiveDevice::None;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "common/bitfield.h"
|
#include "common/bitfield.h"
|
||||||
#include "common/fifo_queue.h"
|
#include "common/fifo_queue.h"
|
||||||
|
#include "multitap.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
@ -28,6 +29,9 @@ public:
|
||||||
MemoryCard* GetMemoryCard(u32 slot) { return m_memory_cards[slot].get(); }
|
MemoryCard* GetMemoryCard(u32 slot) { return m_memory_cards[slot].get(); }
|
||||||
void SetMemoryCard(u32 slot, std::unique_ptr<MemoryCard> dev);
|
void SetMemoryCard(u32 slot, std::unique_ptr<MemoryCard> dev);
|
||||||
|
|
||||||
|
Multitap* GetMultitap(u32 slot) { return &m_multitaps[slot]; };
|
||||||
|
void SetMultitapEnable(u32 port, bool enable);
|
||||||
|
|
||||||
u32 ReadRegister(u32 offset);
|
u32 ReadRegister(u32 offset);
|
||||||
void WriteRegister(u32 offset, u32 value);
|
void WriteRegister(u32 offset, u32 value);
|
||||||
|
|
||||||
|
@ -47,7 +51,8 @@ private:
|
||||||
{
|
{
|
||||||
None,
|
None,
|
||||||
Controller,
|
Controller,
|
||||||
MemoryCard
|
MemoryCard,
|
||||||
|
Multitap
|
||||||
};
|
};
|
||||||
|
|
||||||
union JOY_CTRL
|
union JOY_CTRL
|
||||||
|
@ -108,8 +113,10 @@ private:
|
||||||
void EndTransfer();
|
void EndTransfer();
|
||||||
void ResetDeviceTransferState();
|
void ResetDeviceTransferState();
|
||||||
|
|
||||||
std::array<std::unique_ptr<Controller>, NUM_SLOTS> m_controllers;
|
std::array<std::unique_ptr<Controller>, NUM_CONTROLLER_AND_CARD_PORTS> m_controllers;
|
||||||
std::array<std::unique_ptr<MemoryCard>, NUM_SLOTS> m_memory_cards;
|
std::array<std::unique_ptr<MemoryCard>, NUM_CONTROLLER_AND_CARD_PORTS> m_memory_cards;
|
||||||
|
|
||||||
|
std::array<Multitap, NUM_MULTITAPS> m_multitaps = {Multitap(0), Multitap(1)};
|
||||||
|
|
||||||
std::unique_ptr<TimingEvent> m_transfer_event;
|
std::unique_ptr<TimingEvent> m_transfer_event;
|
||||||
State m_state = State::Idle;
|
State m_state = State::Idle;
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
static constexpr u32 SAVE_STATE_MAGIC = 0x43435544;
|
static constexpr u32 SAVE_STATE_MAGIC = 0x43435544;
|
||||||
static constexpr u32 SAVE_STATE_VERSION = 49;
|
static constexpr u32 SAVE_STATE_VERSION = 50;
|
||||||
static constexpr u32 SAVE_STATE_MINIMUM_VERSION = 42;
|
static constexpr u32 SAVE_STATE_MINIMUM_VERSION = 42;
|
||||||
|
|
||||||
static_assert(SAVE_STATE_VERSION >= SAVE_STATE_MINIMUM_VERSION);
|
static_assert(SAVE_STATE_VERSION >= SAVE_STATE_MINIMUM_VERSION);
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
|
#include "common/assert.h"
|
||||||
#include "common/file_system.h"
|
#include "common/file_system.h"
|
||||||
#include "common/make_array.h"
|
#include "common/make_array.h"
|
||||||
#include "common/string_util.h"
|
#include "common/string_util.h"
|
||||||
|
@ -77,6 +78,31 @@ bool Settings::HasAnyPerGameMemoryCards() const
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Settings::IsMultitapEnabledOnPort(u32 port) const
|
||||||
|
{
|
||||||
|
if (port < NUM_MULTITAPS)
|
||||||
|
{
|
||||||
|
switch (multitap_mode)
|
||||||
|
{
|
||||||
|
case MultitapMode::Disabled:
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MultitapMode::Port1Only:
|
||||||
|
return port == 0u;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MultitapMode::BothPorts:
|
||||||
|
return true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
DefaultCaseIsUnreachable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void Settings::CPUOverclockPercentToFraction(u32 percent, u32* numerator, u32* denominator)
|
void Settings::CPUOverclockPercentToFraction(u32 percent, u32* numerator, u32* denominator)
|
||||||
{
|
{
|
||||||
const u32 percent_gcd = std::gcd(percent, 100);
|
const u32 percent_gcd = std::gcd(percent, 100);
|
||||||
|
@ -231,11 +257,15 @@ void Settings::Load(SettingsInterface& si)
|
||||||
ParseControllerTypeName(
|
ParseControllerTypeName(
|
||||||
si.GetStringValue("Controller1", "Type", GetControllerTypeName(DEFAULT_CONTROLLER_1_TYPE)).c_str())
|
si.GetStringValue("Controller1", "Type", GetControllerTypeName(DEFAULT_CONTROLLER_1_TYPE)).c_str())
|
||||||
.value_or(DEFAULT_CONTROLLER_1_TYPE);
|
.value_or(DEFAULT_CONTROLLER_1_TYPE);
|
||||||
controller_types[1] =
|
|
||||||
ParseControllerTypeName(
|
for (u32 i = 1; i < NUM_CONTROLLER_AND_CARD_PORTS; i++)
|
||||||
si.GetStringValue("Controller2", "Type", GetControllerTypeName(DEFAULT_CONTROLLER_2_TYPE)).c_str())
|
{
|
||||||
|
controller_types[i] =
|
||||||
|
ParseControllerTypeName(si.GetStringValue(TinyString::FromFormat("Controller%u", i + 1u), "Type",
|
||||||
|
GetControllerTypeName(DEFAULT_CONTROLLER_2_TYPE))
|
||||||
|
.c_str())
|
||||||
.value_or(DEFAULT_CONTROLLER_2_TYPE);
|
.value_or(DEFAULT_CONTROLLER_2_TYPE);
|
||||||
controller_disable_analog_mode_forcing = false;
|
}
|
||||||
|
|
||||||
memory_card_types[0] =
|
memory_card_types[0] =
|
||||||
ParseMemoryCardTypeName(
|
ParseMemoryCardTypeName(
|
||||||
|
@ -251,6 +281,11 @@ void Settings::Load(SettingsInterface& si)
|
||||||
si.GetStringValue("MemoryCards", "Card2Path", "memcards" FS_OSPATH_SEPARATOR_STR "shared_card_2.mcd");
|
si.GetStringValue("MemoryCards", "Card2Path", "memcards" FS_OSPATH_SEPARATOR_STR "shared_card_2.mcd");
|
||||||
memory_card_use_playlist_title = si.GetBoolValue("MemoryCards", "UsePlaylistTitle", true);
|
memory_card_use_playlist_title = si.GetBoolValue("MemoryCards", "UsePlaylistTitle", true);
|
||||||
|
|
||||||
|
multitap_mode =
|
||||||
|
ParseMultitapModeName(
|
||||||
|
si.GetStringValue("ControllerPorts", "MultitapMode", GetMultitapModeName(DEFAULT_MULTITAP_MODE)).c_str())
|
||||||
|
.value_or(DEFAULT_MULTITAP_MODE);
|
||||||
|
|
||||||
log_level = ParseLogLevelName(si.GetStringValue("Logging", "LogLevel", GetLogLevelName(DEFAULT_LOG_LEVEL)).c_str())
|
log_level = ParseLogLevelName(si.GetStringValue("Logging", "LogLevel", GetLogLevelName(DEFAULT_LOG_LEVEL)).c_str())
|
||||||
.value_or(DEFAULT_LOG_LEVEL);
|
.value_or(DEFAULT_LOG_LEVEL);
|
||||||
log_filter = si.GetStringValue("Logging", "LogFilter", "");
|
log_filter = si.GetStringValue("Logging", "LogFilter", "");
|
||||||
|
@ -399,6 +434,8 @@ void Settings::Save(SettingsInterface& si) const
|
||||||
si.SetStringValue("MemoryCards", "Card2Path", memory_card_paths[1].c_str());
|
si.SetStringValue("MemoryCards", "Card2Path", memory_card_paths[1].c_str());
|
||||||
si.SetBoolValue("MemoryCards", "UsePlaylistTitle", memory_card_use_playlist_title);
|
si.SetBoolValue("MemoryCards", "UsePlaylistTitle", memory_card_use_playlist_title);
|
||||||
|
|
||||||
|
si.SetStringValue("ControllerPorts", "MultitapMode", GetMultitapModeName(multitap_mode));
|
||||||
|
|
||||||
si.SetStringValue("Logging", "LogLevel", GetLogLevelName(log_level));
|
si.SetStringValue("Logging", "LogLevel", GetLogLevelName(log_level));
|
||||||
si.SetStringValue("Logging", "LogFilter", log_filter.c_str());
|
si.SetStringValue("Logging", "LogFilter", log_filter.c_str());
|
||||||
si.SetBoolValue("Logging", "LogToConsole", log_to_console);
|
si.SetBoolValue("Logging", "LogToConsole", log_to_console);
|
||||||
|
@ -840,3 +877,32 @@ const char* Settings::GetMemoryCardTypeDisplayName(MemoryCardType type)
|
||||||
{
|
{
|
||||||
return s_memory_card_type_display_names[static_cast<int>(type)];
|
return s_memory_card_type_display_names[static_cast<int>(type)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::array<const char*, 3> s_multitap_enable_mode_names = {{"Disabled", "Port1Only", "BothPorts"}};
|
||||||
|
static std::array<const char*, 3> s_multitap_enable_mode_display_names = {
|
||||||
|
{TRANSLATABLE("MultitapMode", "Disabled"), TRANSLATABLE("MultitapMode", "Enable on Port 1 only"),
|
||||||
|
TRANSLATABLE("MultitapMode", "Enable on Ports 1 and 2")}};
|
||||||
|
|
||||||
|
std::optional<MultitapMode> Settings::ParseMultitapModeName(const char* str)
|
||||||
|
{
|
||||||
|
u32 index = 0;
|
||||||
|
for (const char* name : s_multitap_enable_mode_names)
|
||||||
|
{
|
||||||
|
if (StringUtil::Strcasecmp(name, str) == 0)
|
||||||
|
return static_cast<MultitapMode>(index);
|
||||||
|
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* Settings::GetMultitapModeName(MultitapMode mode)
|
||||||
|
{
|
||||||
|
return s_multitap_enable_mode_names[static_cast<size_t>(mode)];
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* Settings::GetMultitapModeDisplayName(MultitapMode mode)
|
||||||
|
{
|
||||||
|
return s_multitap_enable_mode_display_names[static_cast<size_t>(mode)];
|
||||||
|
}
|
||||||
|
|
|
@ -216,6 +216,8 @@ struct Settings
|
||||||
std::array<std::string, NUM_CONTROLLER_AND_CARD_PORTS> memory_card_paths{};
|
std::array<std::string, NUM_CONTROLLER_AND_CARD_PORTS> memory_card_paths{};
|
||||||
bool memory_card_use_playlist_title = true;
|
bool memory_card_use_playlist_title = true;
|
||||||
|
|
||||||
|
MultitapMode multitap_mode = MultitapMode::Disabled;
|
||||||
|
|
||||||
LOGLEVEL log_level = LOGLEVEL_INFO;
|
LOGLEVEL log_level = LOGLEVEL_INFO;
|
||||||
std::string log_filter;
|
std::string log_filter;
|
||||||
bool log_to_console = false;
|
bool log_to_console = false;
|
||||||
|
@ -251,6 +253,8 @@ struct Settings
|
||||||
|
|
||||||
bool HasAnyPerGameMemoryCards() const;
|
bool HasAnyPerGameMemoryCards() const;
|
||||||
|
|
||||||
|
bool IsMultitapEnabledOnPort(u32 port) const;
|
||||||
|
|
||||||
static void CPUOverclockPercentToFraction(u32 percent, u32* numerator, u32* denominator);
|
static void CPUOverclockPercentToFraction(u32 percent, u32* numerator, u32* denominator);
|
||||||
static u32 CPUOverclockFractionToPercent(u32 numerator, u32 denominator);
|
static u32 CPUOverclockFractionToPercent(u32 numerator, u32 denominator);
|
||||||
|
|
||||||
|
@ -323,6 +327,10 @@ struct Settings
|
||||||
static const char* GetMemoryCardTypeName(MemoryCardType type);
|
static const char* GetMemoryCardTypeName(MemoryCardType type);
|
||||||
static const char* GetMemoryCardTypeDisplayName(MemoryCardType type);
|
static const char* GetMemoryCardTypeDisplayName(MemoryCardType type);
|
||||||
|
|
||||||
|
static std::optional<MultitapMode> ParseMultitapModeName(const char* str);
|
||||||
|
static const char* GetMultitapModeName(MultitapMode mode);
|
||||||
|
static const char* GetMultitapModeDisplayName(MultitapMode mode);
|
||||||
|
|
||||||
// Default to D3D11 on Windows as it's more performant and at this point, less buggy.
|
// Default to D3D11 on Windows as it's more performant and at this point, less buggy.
|
||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
static constexpr GPURenderer DEFAULT_GPU_RENDERER = GPURenderer::HardwareD3D11;
|
static constexpr GPURenderer DEFAULT_GPU_RENDERER = GPURenderer::HardwareD3D11;
|
||||||
|
@ -358,6 +366,8 @@ struct Settings
|
||||||
static constexpr ControllerType DEFAULT_CONTROLLER_2_TYPE = ControllerType::None;
|
static constexpr ControllerType DEFAULT_CONTROLLER_2_TYPE = ControllerType::None;
|
||||||
static constexpr MemoryCardType DEFAULT_MEMORY_CARD_1_TYPE = MemoryCardType::PerGameTitle;
|
static constexpr MemoryCardType DEFAULT_MEMORY_CARD_1_TYPE = MemoryCardType::PerGameTitle;
|
||||||
static constexpr MemoryCardType DEFAULT_MEMORY_CARD_2_TYPE = MemoryCardType::None;
|
static constexpr MemoryCardType DEFAULT_MEMORY_CARD_2_TYPE = MemoryCardType::None;
|
||||||
|
static constexpr MultitapMode DEFAULT_MULTITAP_MODE = MultitapMode::Disabled;
|
||||||
|
|
||||||
static constexpr LOGLEVEL DEFAULT_LOG_LEVEL = LOGLEVEL_INFO;
|
static constexpr LOGLEVEL DEFAULT_LOG_LEVEL = LOGLEVEL_INFO;
|
||||||
|
|
||||||
// Enable console logging by default on Linux platforms.
|
// Enable console logging by default on Linux platforms.
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
#include "libcrypt_game_codes.h"
|
#include "libcrypt_game_codes.h"
|
||||||
#include "mdec.h"
|
#include "mdec.h"
|
||||||
#include "memory_card.h"
|
#include "memory_card.h"
|
||||||
|
#include "multitap.h"
|
||||||
#include "pad.h"
|
#include "pad.h"
|
||||||
#include "psf_loader.h"
|
#include "psf_loader.h"
|
||||||
#include "save_state_version.h"
|
#include "save_state_version.h"
|
||||||
|
@ -828,6 +829,7 @@ bool Boot(const SystemBootParameters& params)
|
||||||
Bus::SetBIOS(*bios_image);
|
Bus::SetBIOS(*bios_image);
|
||||||
UpdateControllers();
|
UpdateControllers();
|
||||||
UpdateMemoryCards();
|
UpdateMemoryCards();
|
||||||
|
UpdateMultitaps();
|
||||||
Reset();
|
Reset();
|
||||||
|
|
||||||
// Enable tty by patching bios.
|
// Enable tty by patching bios.
|
||||||
|
@ -1223,6 +1225,7 @@ bool DoLoadState(ByteStream* state, bool force_software_renderer, bool update_di
|
||||||
|
|
||||||
UpdateControllers();
|
UpdateControllers();
|
||||||
UpdateMemoryCards();
|
UpdateMemoryCards();
|
||||||
|
UpdateMultitaps();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -1836,6 +1839,34 @@ void UpdateMemoryCards()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UpdateMultitaps()
|
||||||
|
{
|
||||||
|
switch (g_settings.multitap_mode)
|
||||||
|
{
|
||||||
|
case MultitapMode::Disabled:
|
||||||
|
{
|
||||||
|
g_pad.SetMultitapEnable(0, false);
|
||||||
|
g_pad.SetMultitapEnable(1, false);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MultitapMode::Port1Only:
|
||||||
|
{
|
||||||
|
g_pad.SetMultitapEnable(0, true);
|
||||||
|
g_pad.SetMultitapEnable(1, false);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MultitapMode::BothPorts:
|
||||||
|
{
|
||||||
|
g_pad.SetMultitapEnable(0, true);
|
||||||
|
g_pad.SetMultitapEnable(1, true);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
bool DumpRAM(const char* filename)
|
bool DumpRAM(const char* filename)
|
||||||
{
|
{
|
||||||
if (!IsValid())
|
if (!IsValid())
|
||||||
|
|
|
@ -187,6 +187,7 @@ void UpdateControllers();
|
||||||
void UpdateControllerSettings();
|
void UpdateControllerSettings();
|
||||||
void ResetControllers();
|
void ResetControllers();
|
||||||
void UpdateMemoryCards();
|
void UpdateMemoryCards();
|
||||||
|
void UpdateMultitaps();
|
||||||
|
|
||||||
/// Dumps RAM to a file.
|
/// Dumps RAM to a file.
|
||||||
bool DumpRAM(const char* filename);
|
bool DumpRAM(const char* filename);
|
||||||
|
|
|
@ -143,9 +143,18 @@ enum class MemoryCardType
|
||||||
Count
|
Count
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class MultitapMode
|
||||||
|
{
|
||||||
|
Disabled,
|
||||||
|
Port1Only,
|
||||||
|
BothPorts,
|
||||||
|
Count
|
||||||
|
};
|
||||||
|
|
||||||
enum : u32
|
enum : u32
|
||||||
{
|
{
|
||||||
NUM_CONTROLLER_AND_CARD_PORTS = 2
|
NUM_CONTROLLER_AND_CARD_PORTS = 8,
|
||||||
|
NUM_MULTITAPS = 2
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class CPUFastmemMode
|
enum class CPUFastmemMode
|
||||||
|
|
|
@ -22,6 +22,12 @@ ConsoleSettingsWidget::ConsoleSettingsWidget(QtHostInterface* host_interface, QW
|
||||||
qApp->translate("CPUExecutionMode", Settings::GetCPUExecutionModeDisplayName(static_cast<CPUExecutionMode>(i))));
|
qApp->translate("CPUExecutionMode", Settings::GetCPUExecutionModeDisplayName(static_cast<CPUExecutionMode>(i))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (u32 i = 0; i < static_cast<u32>(MultitapMode::Count); i++)
|
||||||
|
{
|
||||||
|
m_ui.multitapMode->addItem(
|
||||||
|
qApp->translate("MultitapMode", Settings::GetMultitapModeDisplayName(static_cast<MultitapMode>(i))));
|
||||||
|
}
|
||||||
|
|
||||||
SettingWidgetBinder::BindWidgetToEnumSetting(m_host_interface, m_ui.region, "Console", "Region",
|
SettingWidgetBinder::BindWidgetToEnumSetting(m_host_interface, m_ui.region, "Console", "Region",
|
||||||
&Settings::ParseConsoleRegionName, &Settings::GetConsoleRegionName,
|
&Settings::ParseConsoleRegionName, &Settings::GetConsoleRegionName,
|
||||||
Settings::DEFAULT_CONSOLE_REGION);
|
Settings::DEFAULT_CONSOLE_REGION);
|
||||||
|
@ -34,18 +40,18 @@ ConsoleSettingsWidget::ConsoleSettingsWidget(QtHostInterface* host_interface, QW
|
||||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.cdromRegionCheck, "CDROM", "RegionCheck");
|
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.cdromRegionCheck, "CDROM", "RegionCheck");
|
||||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.cdromLoadImageToRAM, "CDROM", "LoadImageToRAM",
|
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.cdromLoadImageToRAM, "CDROM", "LoadImageToRAM",
|
||||||
false);
|
false);
|
||||||
|
SettingWidgetBinder::BindWidgetToEnumSetting(m_host_interface, m_ui.multitapMode, "ControllerPorts", "MultitapMode",
|
||||||
|
&Settings::ParseMultitapModeName, &Settings::GetMultitapModeName,
|
||||||
|
Settings::DEFAULT_MULTITAP_MODE);
|
||||||
|
|
||||||
dialog->registerWidgetHelp(
|
dialog->registerWidgetHelp(m_ui.region, tr("Region"), tr("Auto-Detect"),
|
||||||
m_ui.region, tr("Region"), tr("Auto-Detect"),
|
|
||||||
tr("Determines the emulated hardware type."));
|
tr("Determines the emulated hardware type."));
|
||||||
dialog->registerWidgetHelp(
|
dialog->registerWidgetHelp(m_ui.cpuExecutionMode, tr("Execution Mode"), tr("Recompiler (Fastest)"),
|
||||||
m_ui.cpuExecutionMode, tr("Execution Mode"), tr("Recompiler (Fastest)"),
|
|
||||||
tr("Determines how the emulated CPU executes instructions."));
|
tr("Determines how the emulated CPU executes instructions."));
|
||||||
dialog->registerWidgetHelp(
|
dialog->registerWidgetHelp(m_ui.enableCPUClockSpeedControl,
|
||||||
m_ui.enableCPUClockSpeedControl, tr("Enable Clock Speed Control (Overclocking/Underclocking)"), tr("Unchecked"),
|
tr("Enable Clock Speed Control (Overclocking/Underclocking)"), tr("Unchecked"),
|
||||||
tr("When this option is chosen, the clock speed set below will be used."));
|
tr("When this option is chosen, the clock speed set below will be used."));
|
||||||
dialog->registerWidgetHelp(
|
dialog->registerWidgetHelp(m_ui.cpuClockSpeed, tr("Overclocking Percentage"), tr("100%"),
|
||||||
m_ui.cpuClockSpeed, tr("Overclocking Percentage"), tr("100%"),
|
|
||||||
tr("Selects the percentage of the normal clock speed the emulated hardware will run at."));
|
tr("Selects the percentage of the normal clock speed the emulated hardware will run at."));
|
||||||
dialog->registerWidgetHelp(
|
dialog->registerWidgetHelp(
|
||||||
m_ui.cdromReadSpeedup, tr("CDROM Read Speedup"), tr("None (Double Speed)"),
|
m_ui.cdromReadSpeedup, tr("CDROM Read Speedup"), tr("None (Double Speed)"),
|
||||||
|
@ -54,13 +60,16 @@ ConsoleSettingsWidget::ConsoleSettingsWidget(QtHostInterface* host_interface, QW
|
||||||
dialog->registerWidgetHelp(
|
dialog->registerWidgetHelp(
|
||||||
m_ui.cdromReadThread, tr("Use Read Thread (Asynchronous)"), tr("Checked"),
|
m_ui.cdromReadThread, tr("Use Read Thread (Asynchronous)"), tr("Checked"),
|
||||||
tr("Reduces hitches in emulation by reading/decompressing CD data asynchronously on a worker thread."));
|
tr("Reduces hitches in emulation by reading/decompressing CD data asynchronously on a worker thread."));
|
||||||
dialog->registerWidgetHelp(
|
dialog->registerWidgetHelp(m_ui.cdromRegionCheck, tr("Enable Region Check"), tr("Checked"),
|
||||||
m_ui.cdromRegionCheck, tr("Enable Region Check"), tr("Checked"),
|
|
||||||
tr("Simulates the region check present in original, unmodified consoles."));
|
tr("Simulates the region check present in original, unmodified consoles."));
|
||||||
dialog->registerWidgetHelp(
|
dialog->registerWidgetHelp(
|
||||||
m_ui.cdromLoadImageToRAM, tr("Preload Image to RAM"), tr("Unchecked"),
|
m_ui.cdromLoadImageToRAM, tr("Preload Image to RAM"), tr("Unchecked"),
|
||||||
tr("Loads the game image into RAM. Useful for network paths that may become unreliable during gameplay. In some "
|
tr("Loads the game image into RAM. Useful for network paths that may become unreliable during gameplay. In some "
|
||||||
"cases also eliminates stutter when games initiate audio track playback."));
|
"cases also eliminates stutter when games initiate audio track playback."));
|
||||||
|
dialog->registerWidgetHelp(
|
||||||
|
m_ui.multitapMode, tr("Multitap"), tr("Disabled"),
|
||||||
|
tr("Enables multitap support on specified controller ports. Leave disabled for games that do "
|
||||||
|
"not support multitap input."));
|
||||||
|
|
||||||
m_ui.cpuClockSpeed->setEnabled(m_ui.enableCPUClockSpeedControl->checkState() == Qt::Checked);
|
m_ui.cpuClockSpeed->setEnabled(m_ui.enableCPUClockSpeedControl->checkState() == Qt::Checked);
|
||||||
m_ui.cdromReadSpeedup->setCurrentIndex(m_host_interface->GetIntSettingValue("CDROM", "ReadSpeedup", 1) - 1);
|
m_ui.cdromReadSpeedup->setCurrentIndex(m_host_interface->GetIntSettingValue("CDROM", "ReadSpeedup", 1) - 1);
|
||||||
|
|
|
@ -219,6 +219,25 @@
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="groupBox_3">
|
||||||
|
<property name="title">
|
||||||
|
<string>Controller Ports</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QFormLayout" name="formLayout_2">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="label_4">
|
||||||
|
<property name="text">
|
||||||
|
<string>Multitap:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QComboBox" name="multitapMode"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<spacer name="verticalSpacer">
|
<spacer name="verticalSpacer">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
|
|
|
@ -50,5 +50,5 @@ private:
|
||||||
void onLoadProfileClicked();
|
void onLoadProfileClicked();
|
||||||
void onSaveProfileClicked();
|
void onSaveProfileClicked();
|
||||||
|
|
||||||
std::array<PortSettingsUI, 2> m_port_ui = {};
|
std::array<PortSettingsUI, NUM_CONTROLLER_AND_CARD_PORTS> m_port_ui = {};
|
||||||
};
|
};
|
||||||
|
|
|
@ -1384,7 +1384,7 @@ void CommonHostInterface::UpdateControllerInputMap(SettingsInterface& si)
|
||||||
StopControllerRumble();
|
StopControllerRumble();
|
||||||
m_controller_vibration_motors.clear();
|
m_controller_vibration_motors.clear();
|
||||||
|
|
||||||
for (u32 controller_index = 0; controller_index < 2; controller_index++)
|
for (u32 controller_index = 0; controller_index < NUM_CONTROLLER_AND_CARD_PORTS; controller_index++)
|
||||||
{
|
{
|
||||||
const ControllerType ctype = g_settings.controller_types[controller_index];
|
const ControllerType ctype = g_settings.controller_types[controller_index];
|
||||||
if (ctype == ControllerType::None)
|
if (ctype == ControllerType::None)
|
||||||
|
|
|
@ -1315,6 +1315,11 @@ void DrawSettingsWindow()
|
||||||
"Loads the game image into RAM. Useful for network paths that may become unreliable during gameplay.",
|
"Loads the game image into RAM. Useful for network paths that may become unreliable during gameplay.",
|
||||||
&s_settings_copy.cdrom_load_image_to_ram);
|
&s_settings_copy.cdrom_load_image_to_ram);
|
||||||
|
|
||||||
|
MenuHeading("Controller Ports");
|
||||||
|
|
||||||
|
settings_changed |= EnumChoiceButton("Multitap", nullptr, &s_settings_copy.multitap_mode,
|
||||||
|
&Settings::GetMultitapModeDisplayName, MultitapMode::Count);
|
||||||
|
|
||||||
EndMenuButtons();
|
EndMenuButtons();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -1569,7 +1574,16 @@ void DrawSettingsWindow()
|
||||||
|
|
||||||
for (u32 port = 0; port < NUM_CONTROLLER_AND_CARD_PORTS; port++)
|
for (u32 port = 0; port < NUM_CONTROLLER_AND_CARD_PORTS; port++)
|
||||||
{
|
{
|
||||||
MenuHeading(TinyString::FromFormat("Controller Port %u", port + 1));
|
u32 console_port = port / 4u;
|
||||||
|
if (s_settings_copy.IsMultitapEnabledOnPort(console_port))
|
||||||
|
MenuHeading(TinyString::FromFormat("Port %u%c", console_port + 1u, 'A' + (port % 4u)));
|
||||||
|
else if (port < 2u)
|
||||||
|
MenuHeading(TinyString::FromFormat("Port %u", port + 1u));
|
||||||
|
else if (port % 4u == 0u && s_settings_copy.IsMultitapEnabledOnPort(0))
|
||||||
|
MenuHeading(TinyString::FromFormat("Port %u", console_port + 1u));
|
||||||
|
else
|
||||||
|
continue;
|
||||||
|
|
||||||
settings_changed |= EnumChoiceButton(
|
settings_changed |= EnumChoiceButton(
|
||||||
TinyString::FromFormat(ICON_FA_GAMEPAD " Controller Type##type%u", port),
|
TinyString::FromFormat(ICON_FA_GAMEPAD " Controller Type##type%u", port),
|
||||||
"Determines the simulated controller plugged into this port.", &s_settings_copy.controller_types[port],
|
"Determines the simulated controller plugged into this port.", &s_settings_copy.controller_types[port],
|
||||||
|
|
Loading…
Reference in New Issue