WII_IPC_HLE/57e_305: Fake CommandReadBufferSize replies

Apparently, Nintendo's Bluetooth stack expects the ACL packet buffer
size to be limited to 10.

Reporting anything higher than that could cause memory corruption,
which can result in warning messages in the logs ("event mismatch"),
and more annoyingly, random disconnects.

Thanks to shuffle2 for the fix!
This commit is contained in:
Léo Lam 2016-09-11 12:34:14 +02:00
parent 4b47997cf8
commit 419a9c55e4
5 changed files with 52 additions and 9 deletions

View File

@ -28,6 +28,11 @@ public:
virtual void TriggerSyncButtonPressedEvent() {} virtual void TriggerSyncButtonPressedEvent() {}
virtual void TriggerSyncButtonHeldEvent() {} virtual void TriggerSyncButtonHeldEvent() {}
protected: protected:
static constexpr int ACL_PKT_SIZE = 339;
static constexpr int ACL_PKT_NUM = 10;
static constexpr int SCO_PKT_SIZE = 64;
static constexpr int SCO_PKT_NUM = 0;
enum USBIOCtl enum USBIOCtl
{ {
USBV0_IOCTL_CTRLMSG = 0, USBV0_IOCTL_CTRLMSG = 0,

View File

@ -521,7 +521,7 @@ void CWII_IPC_HLE_Device_usb_oh1_57e_305_emu::ACLPool::Store(const u8* data, con
return; return;
} }
_dbg_assert_msg_(WII_IPC_WIIMOTE, size < m_acl_pkt_size, "ACL packet too large for pool"); _dbg_assert_msg_(WII_IPC_WIIMOTE, size < ACL_PKT_SIZE, "ACL packet too large for pool");
m_queue.push_back(Packet()); m_queue.push_back(Packet());
auto& packet = m_queue.back(); auto& packet = m_queue.back();
@ -1824,13 +1824,13 @@ void CWII_IPC_HLE_Device_usb_oh1_57e_305_emu::CommandReadBufferSize(const u8* in
{ {
hci_read_buffer_size_rp reply; hci_read_buffer_size_rp reply;
reply.status = 0x00; reply.status = 0x00;
reply.max_acl_size = m_acl_pkt_size; reply.max_acl_size = ACL_PKT_SIZE;
// Due to how the widcomm stack which Nintendo uses is coded, we must never // Due to how the widcomm stack which Nintendo uses is coded, we must never
// let the stack think the controller is buffering more than 10 data packets // let the stack think the controller is buffering more than 10 data packets
// - it will cause a u8 underflow and royally screw things up. // - it will cause a u8 underflow and royally screw things up.
reply.num_acl_pkts = m_acl_pkts_num; reply.num_acl_pkts = ACL_PKT_NUM;
reply.max_sco_size = 64; reply.max_sco_size = SCO_PKT_SIZE;
reply.num_sco_pkts = 0; reply.num_sco_pkts = SCO_PKT_NUM;
INFO_LOG(WII_IPC_WIIMOTE, "Command: HCI_CMD_READ_BUFFER_SIZE:"); INFO_LOG(WII_IPC_WIIMOTE, "Command: HCI_CMD_READ_BUFFER_SIZE:");
DEBUG_LOG(WII_IPC_WIIMOTE, "return:"); DEBUG_LOG(WII_IPC_WIIMOTE, "return:");

View File

@ -95,14 +95,11 @@ private:
u32 m_ACLSetup; u32 m_ACLSetup;
CtrlBuffer m_ACLEndpoint; CtrlBuffer m_ACLEndpoint;
static const int m_acl_pkt_size = 339;
static const int m_acl_pkts_num = 10;
class ACLPool class ACLPool
{ {
struct Packet struct Packet
{ {
u8 data[m_acl_pkt_size]; u8 data[ACL_PKT_SIZE];
u16 size; u16 size;
u16 conn_handle; u16 conn_handle;
}; };

View File

@ -140,6 +140,12 @@ IPCCommandResult CWII_IPC_HLE_Device_usb_oh1_57e_305_real::IOCtlV(u32 command_ad
case USBV0_IOCTL_CTRLMSG: case USBV0_IOCTL_CTRLMSG:
{ {
auto cmd = std::make_unique<CtrlMessage>(cmd_buffer); auto cmd = std::make_unique<CtrlMessage>(cmd_buffer);
const u16 opcode = *reinterpret_cast<u16*>(Memory::GetPointer(cmd->payload_addr));
if (opcode == HCI_CMD_READ_BUFFER_SIZE)
{
m_fake_read_buffer_size_reply.Set();
return GetNoReply();
}
auto buffer = std::vector<u8>(cmd->length + LIBUSB_CONTROL_SETUP_SIZE); auto buffer = std::vector<u8>(cmd->length + LIBUSB_CONTROL_SETUP_SIZE);
libusb_fill_control_setup(buffer.data(), cmd->request_type, cmd->request, cmd->value, libusb_fill_control_setup(buffer.data(), cmd->request_type, cmd->request, cmd->value,
cmd->index, cmd->length); cmd->index, cmd->length);
@ -156,6 +162,11 @@ IPCCommandResult CWII_IPC_HLE_Device_usb_oh1_57e_305_real::IOCtlV(u32 command_ad
case USBV0_IOCTL_INTRMSG: case USBV0_IOCTL_INTRMSG:
{ {
auto buffer = std::make_unique<CtrlBuffer>(cmd_buffer, command_address); auto buffer = std::make_unique<CtrlBuffer>(cmd_buffer, command_address);
if (cmd_buffer.Parameter == USBV0_IOCTL_INTRMSG && m_fake_read_buffer_size_reply.TestAndClear())
{
FakeReadBufferSizeReply(*buffer);
return GetNoReply();
}
if (cmd_buffer.Parameter == USBV0_IOCTL_INTRMSG && if (cmd_buffer.Parameter == USBV0_IOCTL_INTRMSG &&
m_sync_button_state == SyncButtonState::Pressed) m_sync_button_state == SyncButtonState::Pressed)
{ {
@ -241,6 +252,32 @@ void CWII_IPC_HLE_Device_usb_oh1_57e_305_real::SendHCIResetCommand()
INFO_LOG(WII_IPC_WIIMOTE, "Sent a reset command to adapter"); INFO_LOG(WII_IPC_WIIMOTE, "Sent a reset command to adapter");
} }
// Due to how the widcomm stack which Nintendo uses is coded, we must never
// let the stack think the controller is buffering more than 10 data packets
// - it will cause a u8 underflow and royally screw things up.
// Therefore, the reply to this command has to be faked to avoid random, weird issues
// (including Wiimote disconnects and "event mismatch" warning messages).
void CWII_IPC_HLE_Device_usb_oh1_57e_305_real::FakeReadBufferSizeReply(const CtrlBuffer& ctrl)
{
u8* packet = Memory::GetPointer(ctrl.m_payload_addr);
auto* hci_event = reinterpret_cast<SHCIEventCommand*>(packet);
hci_event->EventType = HCI_EVENT_COMMAND_COMPL;
hci_event->PayloadLength = sizeof(SHCIEventCommand) - 2 + sizeof(hci_read_buffer_size_rp);
hci_event->PacketIndicator = 0x01;
hci_event->Opcode = HCI_CMD_READ_BUFFER_SIZE;
hci_read_buffer_size_rp reply;
reply.status = 0x00;
reply.max_acl_size = ACL_PKT_SIZE;
reply.num_acl_pkts = ACL_PKT_NUM;
reply.max_sco_size = SCO_PKT_SIZE;
reply.num_sco_pkts = SCO_PKT_NUM;
memcpy(packet + sizeof(SHCIEventCommand), &reply, sizeof(hci_read_buffer_size_rp));
ctrl.SetRetVal(sizeof(SHCIEventCommand) + sizeof(hci_read_buffer_size_rp));
EnqueueReply(ctrl.m_cmd_address);
}
void CWII_IPC_HLE_Device_usb_oh1_57e_305_real::FakeSyncButtonEvent(const CtrlBuffer& ctrl, void CWII_IPC_HLE_Device_usb_oh1_57e_305_real::FakeSyncButtonEvent(const CtrlBuffer& ctrl,
const u8* payload, const u8 size) const u8* payload, const u8 size)
{ {

View File

@ -64,7 +64,11 @@ private:
Common::Flag m_thread_running; Common::Flag m_thread_running;
std::thread m_thread; std::thread m_thread;
// Set when we received a command to read the buffer size, and we need to fake a reply
Common::Flag m_fake_read_buffer_size_reply;
void SendHCIResetCommand(); void SendHCIResetCommand();
void FakeReadBufferSizeReply(const CtrlBuffer& ctrl);
void FakeSyncButtonEvent(const CtrlBuffer& ctrl, const u8* payload, u8 size); void FakeSyncButtonEvent(const CtrlBuffer& ctrl, const u8* payload, u8 size);
void FakeSyncButtonPressedEvent(const CtrlBuffer& ctrl); void FakeSyncButtonPressedEvent(const CtrlBuffer& ctrl);
void FakeSyncButtonHeldEvent(const CtrlBuffer& ctrl); void FakeSyncButtonHeldEvent(const CtrlBuffer& ctrl);