Merge pull request #1697 from ggrtk/multitap
Core: Add Multitap support
This commit is contained in:
commit
86f44f826d
|
@ -67,6 +67,8 @@ add_library(core
|
|||
memory_card.h
|
||||
memory_card_image.cpp
|
||||
memory_card_image.h
|
||||
multitap.cpp
|
||||
multitap.h
|
||||
namco_guncon.cpp
|
||||
namco_guncon.h
|
||||
negcon.cpp
|
||||
|
|
|
@ -136,6 +136,7 @@
|
|||
<ClCompile Include="mdec.cpp" />
|
||||
<ClCompile Include="memory_card.cpp" />
|
||||
<ClCompile Include="memory_card_image.cpp" />
|
||||
<ClCompile Include="multitap.cpp" />
|
||||
<ClCompile Include="namco_guncon.cpp" />
|
||||
<ClCompile Include="negcon.cpp" />
|
||||
<ClCompile Include="pad.cpp" />
|
||||
|
@ -213,6 +214,7 @@
|
|||
<ClInclude Include="mdec.h" />
|
||||
<ClInclude Include="memory_card.h" />
|
||||
<ClInclude Include="memory_card_image.h" />
|
||||
<ClInclude Include="multitap.h" />
|
||||
<ClInclude Include="namco_guncon.h" />
|
||||
<ClInclude Include="negcon.h" />
|
||||
<ClInclude Include="pad.h" />
|
||||
|
|
|
@ -57,6 +57,7 @@
|
|||
<ClCompile Include="libcrypt_game_codes.cpp" />
|
||||
<ClCompile Include="texture_replacements.cpp" />
|
||||
<ClCompile Include="gdb_protocol.h" />
|
||||
<ClCompile Include="multitap.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="types.h" />
|
||||
|
@ -116,5 +117,6 @@
|
|||
<ClInclude Include="libcrypt_game_codes.h" />
|
||||
<ClInclude Include="texture_replacements.h" />
|
||||
<ClInclude Include="shader_cache_version.h" />
|
||||
<ClInclude Include="multitap.h" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -580,7 +580,12 @@ void HostInterface::SetDefaultSettings(SettingsInterface& si)
|
|||
si.SetBoolValue("BIOS", "PatchFastBoot", false);
|
||||
|
||||
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", "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.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", "LogFilter", "");
|
||||
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)
|
||||
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;
|
||||
};
|
100
src/core/pad.cpp
100
src/core/pad.cpp
|
@ -5,6 +5,7 @@
|
|||
#include "host_interface.h"
|
||||
#include "interrupt_controller.h"
|
||||
#include "memory_card.h"
|
||||
#include "multitap.h"
|
||||
#include "system.h"
|
||||
Log_SetChannel(Pad);
|
||||
|
||||
|
@ -27,7 +28,7 @@ void Pad::Shutdown()
|
|||
{
|
||||
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_memory_cards[i].reset();
|
||||
|
@ -38,7 +39,7 @@ void Pad::Reset()
|
|||
{
|
||||
SoftReset();
|
||||
|
||||
for (u32 i = 0; i < NUM_SLOTS; i++)
|
||||
for (u32 i = 0; i < NUM_CONTROLLER_AND_CARD_PORTS; i++)
|
||||
{
|
||||
if (m_controllers[i])
|
||||
m_controllers[i]->Reset();
|
||||
|
@ -46,12 +47,18 @@ void Pad::Reset()
|
|||
if (m_memory_cards[i])
|
||||
m_memory_cards[i]->Reset();
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < NUM_MULTITAPS; i++)
|
||||
m_multitaps[i].Reset();
|
||||
}
|
||||
|
||||
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 state_controller_type = 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_JOY_CTRL.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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
switch (offset)
|
||||
|
@ -409,8 +434,9 @@ void Pad::DoTransfer(TickCount ticks_late)
|
|||
{
|
||||
Log_DebugPrintf("Transferring slot %d", m_JOY_CTRL.SLOT.GetValue());
|
||||
|
||||
Controller* const controller = m_controllers[m_JOY_CTRL.SLOT].get();
|
||||
MemoryCard* const memory_card = m_memory_cards[m_JOY_CTRL.SLOT].get();
|
||||
const u8 device_index = m_multitaps[0].IsEnabled() ? 4u : m_JOY_CTRL.SLOT;
|
||||
Controller* const controller = m_controllers[device_index].get();
|
||||
MemoryCard* const memory_card = m_memory_cards[device_index].get();
|
||||
|
||||
// set rx?
|
||||
m_JOY_CTRL.RXEN = true;
|
||||
|
@ -424,25 +450,37 @@ void Pad::DoTransfer(TickCount ticks_late)
|
|||
{
|
||||
case ActiveDevice::None:
|
||||
{
|
||||
if (!controller || (ack = controller->Transfer(data_out, &data_in)) == false)
|
||||
if (m_multitaps[m_JOY_CTRL.SLOT].IsEnabled())
|
||||
{
|
||||
if (!memory_card || (ack = memory_card->Transfer(data_out, &data_in)) == false)
|
||||
if ((ack = m_multitaps[m_JOY_CTRL.SLOT].Transfer(data_out, &data_in)) == true)
|
||||
{
|
||||
// nothing connected to this port
|
||||
Log_TracePrintf("Nothing connected or ACK'ed");
|
||||
}
|
||||
else
|
||||
{
|
||||
// memory card responded, make it the active device until non-ack
|
||||
Log_TracePrintf("Transfer to memory card, data_out=0x%02X, data_in=0x%02X", data_out, data_in);
|
||||
m_active_device = ActiveDevice::MemoryCard;
|
||||
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
|
||||
{
|
||||
// controller responded, make it the active device until non-ack
|
||||
Log_TracePrintf("Transfer to controller, data_out=0x%02X, data_in=0x%02X", data_out, data_in);
|
||||
m_active_device = ActiveDevice::Controller;
|
||||
if (!controller || (ack = controller->Transfer(data_out, &data_in)) == false)
|
||||
{
|
||||
if (!memory_card || (ack = memory_card->Transfer(data_out, &data_in)) == false)
|
||||
{
|
||||
// nothing connected to this port
|
||||
Log_TracePrintf("Nothing connected or ACK'ed");
|
||||
}
|
||||
else
|
||||
{
|
||||
// memory card responded, make it the active device until non-ack
|
||||
Log_TracePrintf("Transfer to memory card, data_out=0x%02X, data_in=0x%02X", data_out, data_in);
|
||||
m_active_device = ActiveDevice::MemoryCard;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// controller responded, make it the active device until non-ack
|
||||
Log_TracePrintf("Transfer to controller, data_out=0x%02X, data_in=0x%02X", data_out, data_in);
|
||||
m_active_device = ActiveDevice::Controller;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -466,6 +504,17 @@ void Pad::DoTransfer(TickCount ticks_late)
|
|||
}
|
||||
}
|
||||
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;
|
||||
|
@ -479,7 +528,11 @@ void Pad::DoTransfer(TickCount ticks_late)
|
|||
}
|
||||
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);
|
||||
m_state = State::WaitingForACK;
|
||||
m_transfer_event->SetPeriodAndSchedule(ack_timer);
|
||||
|
@ -517,13 +570,16 @@ void Pad::EndTransfer()
|
|||
|
||||
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])
|
||||
m_controllers[i]->ResetTransferState();
|
||||
if (m_memory_cards[i])
|
||||
m_memory_cards[i]->ResetTransferState();
|
||||
|
||||
m_active_device = ActiveDevice::None;
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < NUM_MULTITAPS; i++)
|
||||
m_multitaps[i].ResetTransferState();
|
||||
|
||||
m_active_device = ActiveDevice::None;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
#include "common/bitfield.h"
|
||||
#include "common/fifo_queue.h"
|
||||
#include "multitap.h"
|
||||
#include "types.h"
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
@ -28,6 +29,9 @@ public:
|
|||
MemoryCard* GetMemoryCard(u32 slot) { return m_memory_cards[slot].get(); }
|
||||
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);
|
||||
void WriteRegister(u32 offset, u32 value);
|
||||
|
||||
|
@ -47,7 +51,8 @@ private:
|
|||
{
|
||||
None,
|
||||
Controller,
|
||||
MemoryCard
|
||||
MemoryCard,
|
||||
Multitap
|
||||
};
|
||||
|
||||
union JOY_CTRL
|
||||
|
@ -108,8 +113,10 @@ private:
|
|||
void EndTransfer();
|
||||
void ResetDeviceTransferState();
|
||||
|
||||
std::array<std::unique_ptr<Controller>, NUM_SLOTS> m_controllers;
|
||||
std::array<std::unique_ptr<MemoryCard>, NUM_SLOTS> m_memory_cards;
|
||||
std::array<std::unique_ptr<Controller>, NUM_CONTROLLER_AND_CARD_PORTS> m_controllers;
|
||||
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;
|
||||
State m_state = State::Idle;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
#include "types.h"
|
||||
|
||||
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_assert(SAVE_STATE_VERSION >= SAVE_STATE_MINIMUM_VERSION);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "settings.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/file_system.h"
|
||||
#include "common/make_array.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)
|
||||
{
|
||||
const u32 percent_gcd = std::gcd(percent, 100);
|
||||
|
@ -231,11 +257,15 @@ void Settings::Load(SettingsInterface& si)
|
|||
ParseControllerTypeName(
|
||||
si.GetStringValue("Controller1", "Type", GetControllerTypeName(DEFAULT_CONTROLLER_1_TYPE)).c_str())
|
||||
.value_or(DEFAULT_CONTROLLER_1_TYPE);
|
||||
controller_types[1] =
|
||||
ParseControllerTypeName(
|
||||
si.GetStringValue("Controller2", "Type", GetControllerTypeName(DEFAULT_CONTROLLER_2_TYPE)).c_str())
|
||||
.value_or(DEFAULT_CONTROLLER_2_TYPE);
|
||||
controller_disable_analog_mode_forcing = false;
|
||||
|
||||
for (u32 i = 1; i < NUM_CONTROLLER_AND_CARD_PORTS; i++)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
memory_card_types[0] =
|
||||
ParseMemoryCardTypeName(
|
||||
|
@ -251,6 +281,11 @@ void Settings::Load(SettingsInterface& si)
|
|||
si.GetStringValue("MemoryCards", "Card2Path", "memcards" FS_OSPATH_SEPARATOR_STR "shared_card_2.mcd");
|
||||
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())
|
||||
.value_or(DEFAULT_LOG_LEVEL);
|
||||
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.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", "LogFilter", log_filter.c_str());
|
||||
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)];
|
||||
}
|
||||
|
||||
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{};
|
||||
bool memory_card_use_playlist_title = true;
|
||||
|
||||
MultitapMode multitap_mode = MultitapMode::Disabled;
|
||||
|
||||
LOGLEVEL log_level = LOGLEVEL_INFO;
|
||||
std::string log_filter;
|
||||
bool log_to_console = false;
|
||||
|
@ -251,6 +253,8 @@ struct Settings
|
|||
|
||||
bool HasAnyPerGameMemoryCards() const;
|
||||
|
||||
bool IsMultitapEnabledOnPort(u32 port) const;
|
||||
|
||||
static void CPUOverclockPercentToFraction(u32 percent, 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* 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.
|
||||
#ifdef WIN32
|
||||
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 MemoryCardType DEFAULT_MEMORY_CARD_1_TYPE = MemoryCardType::PerGameTitle;
|
||||
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;
|
||||
|
||||
// Enable console logging by default on Linux platforms.
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include "libcrypt_game_codes.h"
|
||||
#include "mdec.h"
|
||||
#include "memory_card.h"
|
||||
#include "multitap.h"
|
||||
#include "pad.h"
|
||||
#include "psf_loader.h"
|
||||
#include "save_state_version.h"
|
||||
|
@ -828,6 +829,7 @@ bool Boot(const SystemBootParameters& params)
|
|||
Bus::SetBIOS(*bios_image);
|
||||
UpdateControllers();
|
||||
UpdateMemoryCards();
|
||||
UpdateMultitaps();
|
||||
Reset();
|
||||
|
||||
// Enable tty by patching bios.
|
||||
|
@ -1223,6 +1225,7 @@ bool DoLoadState(ByteStream* state, bool force_software_renderer, bool update_di
|
|||
|
||||
UpdateControllers();
|
||||
UpdateMemoryCards();
|
||||
UpdateMultitaps();
|
||||
}
|
||||
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)
|
||||
{
|
||||
if (!IsValid())
|
||||
|
|
|
@ -187,6 +187,7 @@ void UpdateControllers();
|
|||
void UpdateControllerSettings();
|
||||
void ResetControllers();
|
||||
void UpdateMemoryCards();
|
||||
void UpdateMultitaps();
|
||||
|
||||
/// Dumps RAM to a file.
|
||||
bool DumpRAM(const char* filename);
|
||||
|
|
|
@ -143,9 +143,18 @@ enum class MemoryCardType
|
|||
Count
|
||||
};
|
||||
|
||||
enum class MultitapMode
|
||||
{
|
||||
Disabled,
|
||||
Port1Only,
|
||||
BothPorts,
|
||||
Count
|
||||
};
|
||||
|
||||
enum : u32
|
||||
{
|
||||
NUM_CONTROLLER_AND_CARD_PORTS = 2
|
||||
NUM_CONTROLLER_AND_CARD_PORTS = 8,
|
||||
NUM_MULTITAPS = 2
|
||||
};
|
||||
|
||||
enum class CPUFastmemMode
|
||||
|
|
|
@ -22,6 +22,12 @@ ConsoleSettingsWidget::ConsoleSettingsWidget(QtHostInterface* host_interface, QW
|
|||
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",
|
||||
&Settings::ParseConsoleRegionName, &Settings::GetConsoleRegionName,
|
||||
Settings::DEFAULT_CONSOLE_REGION);
|
||||
|
@ -34,19 +40,19 @@ ConsoleSettingsWidget::ConsoleSettingsWidget(QtHostInterface* host_interface, QW
|
|||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.cdromRegionCheck, "CDROM", "RegionCheck");
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.cdromLoadImageToRAM, "CDROM", "LoadImageToRAM",
|
||||
false);
|
||||
SettingWidgetBinder::BindWidgetToEnumSetting(m_host_interface, m_ui.multitapMode, "ControllerPorts", "MultitapMode",
|
||||
&Settings::ParseMultitapModeName, &Settings::GetMultitapModeName,
|
||||
Settings::DEFAULT_MULTITAP_MODE);
|
||||
|
||||
dialog->registerWidgetHelp(
|
||||
m_ui.region, tr("Region"), tr("Auto-Detect"),
|
||||
tr("Determines the emulated hardware type."));
|
||||
dialog->registerWidgetHelp(
|
||||
m_ui.cpuExecutionMode, tr("Execution Mode"), tr("Recompiler (Fastest)"),
|
||||
tr("Determines how the emulated CPU executes instructions."));
|
||||
dialog->registerWidgetHelp(
|
||||
m_ui.enableCPUClockSpeedControl, tr("Enable Clock Speed Control (Overclocking/Underclocking)"), tr("Unchecked"),
|
||||
tr("When this option is chosen, the clock speed set below will be used."));
|
||||
dialog->registerWidgetHelp(
|
||||
m_ui.cpuClockSpeed, tr("Overclocking Percentage"), tr("100%"),
|
||||
tr("Selects the percentage of the normal clock speed the emulated hardware will run at."));
|
||||
dialog->registerWidgetHelp(m_ui.region, tr("Region"), tr("Auto-Detect"),
|
||||
tr("Determines the emulated hardware type."));
|
||||
dialog->registerWidgetHelp(m_ui.cpuExecutionMode, tr("Execution Mode"), tr("Recompiler (Fastest)"),
|
||||
tr("Determines how the emulated CPU executes instructions."));
|
||||
dialog->registerWidgetHelp(m_ui.enableCPUClockSpeedControl,
|
||||
tr("Enable Clock Speed Control (Overclocking/Underclocking)"), tr("Unchecked"),
|
||||
tr("When this option is chosen, the clock speed set below will be used."));
|
||||
dialog->registerWidgetHelp(m_ui.cpuClockSpeed, tr("Overclocking Percentage"), tr("100%"),
|
||||
tr("Selects the percentage of the normal clock speed the emulated hardware will run at."));
|
||||
dialog->registerWidgetHelp(
|
||||
m_ui.cdromReadSpeedup, tr("CDROM Read Speedup"), tr("None (Double Speed)"),
|
||||
tr("Speeds up CD-ROM reads by the specified factor. Only applies to double-speed reads, and is ignored when audio "
|
||||
|
@ -54,13 +60,16 @@ ConsoleSettingsWidget::ConsoleSettingsWidget(QtHostInterface* host_interface, QW
|
|||
dialog->registerWidgetHelp(
|
||||
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."));
|
||||
dialog->registerWidgetHelp(
|
||||
m_ui.cdromRegionCheck, tr("Enable Region Check"), tr("Checked"),
|
||||
tr("Simulates the region check present in original, unmodified consoles."));
|
||||
dialog->registerWidgetHelp(m_ui.cdromRegionCheck, tr("Enable Region Check"), tr("Checked"),
|
||||
tr("Simulates the region check present in original, unmodified consoles."));
|
||||
dialog->registerWidgetHelp(
|
||||
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 "
|
||||
"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.cdromReadSpeedup->setCurrentIndex(m_host_interface->GetIntSettingValue("CDROM", "ReadSpeedup", 1) - 1);
|
||||
|
|
|
@ -219,6 +219,25 @@
|
|||
</layout>
|
||||
</widget>
|
||||
</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>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
|
|
|
@ -50,5 +50,5 @@ private:
|
|||
void onLoadProfileClicked();
|
||||
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();
|
||||
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];
|
||||
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.",
|
||||
&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();
|
||||
}
|
||||
break;
|
||||
|
@ -1569,7 +1574,16 @@ void DrawSettingsWindow()
|
|||
|
||||
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(
|
||||
TinyString::FromFormat(ICON_FA_GAMEPAD " Controller Type##type%u", port),
|
||||
"Determines the simulated controller plugged into this port.", &s_settings_copy.controller_types[port],
|
||||
|
|
Loading…
Reference in New Issue