mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-12-04 19:45:41 +00:00
247 lines
6.5 KiB
C++
247 lines
6.5 KiB
C++
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com> and contributors.
|
|
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
|
|
|
#include "multitap.h"
|
|
#include "common/log.h"
|
|
#include "common/types.h"
|
|
#include "controller.h"
|
|
#include "memory_card.h"
|
|
#include "pad.h"
|
|
#include "util/state_wrapper.h"
|
|
Log_SetChannel(Multitap);
|
|
|
|
Multitap::Multitap()
|
|
{
|
|
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);
|
|
}
|
|
|
|
void Multitap::SetEnable(bool enable, u32 base_index)
|
|
{
|
|
if (m_enabled != enable || m_base_index != base_index)
|
|
{
|
|
m_enabled = enable;
|
|
m_base_index = base_index;
|
|
Reset();
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
bool Multitap::TransferController(u32 slot, const u8 data_in, u8* data_out) const
|
|
{
|
|
Controller* const selected_controller = Pad::GetController(m_base_index + 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 = Pad::GetMemoryCard(m_base_index + 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 gets 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;
|
|
}
|