Skip to content

Commit

Permalink
Add a pairing utility for Wiimotes to Cemu
Browse files Browse the repository at this point in the history
As of now, this is only available on Windows.
This also successfully pairs Wii U Pro Controllers, but since Cemu's
  Wiimote implementation doesn't support Classic Controllers, it doesn't
  fully work.
  • Loading branch information
yeah-its-gloria committed Aug 16, 2023
1 parent 85aa4f0 commit b2ba9c3
Show file tree
Hide file tree
Showing 4 changed files with 285 additions and 0 deletions.
6 changes: 6 additions & 0 deletions src/gui/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ add_library(CemuGui
MemorySearcherTool.h
PadViewFrame.cpp
PadViewFrame.h
PairingDialog.cpp
PairingDialog.h
TitleManager.cpp
TitleManager.h
windows/PPCThreadsViewer
Expand Down Expand Up @@ -170,3 +172,7 @@ if (ENABLE_WXWIDGETS)
# PUBLIC because wx/app.h is included in CemuApp.h
target_link_libraries(CemuGui PUBLIC wx::base wx::core wx::gl wx::propgrid wx::xrc)
endif()

if(WIN32)
target_link_libraries(CemuGui PRIVATE bthprops)
endif()
10 changes: 10 additions & 0 deletions src/gui/MainWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
#include "util/helpers/SystemException.h"
#include "gui/DownloadGraphicPacksWindow.h"
#include "gui/GettingStartedDialog.h"
#include "gui/PairingDialog.h"
#include "gui/helpers/wxHelpers.h"
#include "Cafe/HW/Latte/Renderer/Vulkan/VsyncDriver.h"
#include "gui/input/InputSettings2.h"
Expand Down Expand Up @@ -115,6 +116,7 @@ enum
MAINFRAME_MENU_ID_TOOLS_MEMORY_SEARCHER = 20600,
MAINFRAME_MENU_ID_TOOLS_TITLE_MANAGER,
MAINFRAME_MENU_ID_TOOLS_DOWNLOAD_MANAGER,
MAINFRAME_MENU_ID_TOOLS_PAIR_WIIMOTE,
// cpu
// cpu->timer speed
MAINFRAME_MENU_ID_TIMER_SPEED_1X = 20700,
Expand Down Expand Up @@ -193,6 +195,7 @@ EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_INPUT, MainWindow::OnOptionsInput)
EVT_MENU(MAINFRAME_MENU_ID_TOOLS_MEMORY_SEARCHER, MainWindow::OnToolsInput)
EVT_MENU(MAINFRAME_MENU_ID_TOOLS_TITLE_MANAGER, MainWindow::OnToolsInput)
EVT_MENU(MAINFRAME_MENU_ID_TOOLS_DOWNLOAD_MANAGER, MainWindow::OnToolsInput)
EVT_MENU(MAINFRAME_MENU_ID_TOOLS_PAIR_WIIMOTE, MainWindow::OnToolsInput)
// cpu menu
EVT_MENU(MAINFRAME_MENU_ID_TIMER_SPEED_8X, MainWindow::OnDebugSetting)
EVT_MENU(MAINFRAME_MENU_ID_TIMER_SPEED_4X, MainWindow::OnDebugSetting)
Expand Down Expand Up @@ -1512,7 +1515,13 @@ void MainWindow::OnToolsInput(wxCommandEvent& event)
});
m_title_manager->Show();
}
break;
}
case MAINFRAME_MENU_ID_TOOLS_PAIR_WIIMOTE:
{
PairingDialog pairing_dialog(this);
pairing_dialog.ShowModal();
}
break;
}
}
Expand Down Expand Up @@ -2160,6 +2169,7 @@ void MainWindow::RecreateMenu()
m_memorySearcherMenuItem->Enable(false);
toolsMenu->Append(MAINFRAME_MENU_ID_TOOLS_TITLE_MANAGER, _("&Title Manager"));
toolsMenu->Append(MAINFRAME_MENU_ID_TOOLS_DOWNLOAD_MANAGER, _("&Download Manager"));
toolsMenu->Append(MAINFRAME_MENU_ID_TOOLS_PAIR_WIIMOTE, _("&Pair a Wiimote or Wii U Pro Controller..."));
m_menuBar->Append(toolsMenu, _("&Tools"));

// cpu timer speed menu
Expand Down
234 changes: 234 additions & 0 deletions src/gui/PairingDialog.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
#include "gui/wxgui.h"
#include "gui/PairingDialog.h"

#if BOOST_OS_WINDOWS
#include <bluetoothapis.h>
#endif

wxDECLARE_EVENT(wxEVT_PROGRESS_PAIR, wxCommandEvent);
wxDEFINE_EVENT(wxEVT_PROGRESS_PAIR, wxCommandEvent);

PairingDialog::PairingDialog(wxWindow* parent)
: wxDialog(parent, wxID_ANY, "Pairing...", wxDefaultPosition, wxDefaultSize, wxCAPTION | wxMINIMIZE_BOX | wxSYSTEM_MENU | wxTAB_TRAVERSAL | wxCLOSE_BOX)
{
auto* sizer = new wxBoxSizer(wxVERTICAL);
m_gauge = new wxGauge(this, wxID_ANY, 100, wxDefaultPosition, wxSize(350, 20), wxGA_HORIZONTAL);
m_gauge->SetValue(0);
sizer->Add(m_gauge, 0, wxALL | wxEXPAND, 5);

auto* rows = new wxFlexGridSizer(0, 2, 0, 0);
rows->AddGrowableCol(1);

m_text = new wxStaticText(this, wxID_ANY, "Searching for controllers...");
rows->Add(m_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);

{
auto* right_side = new wxBoxSizer(wxHORIZONTAL);

m_cancelButton = new wxButton(this, wxID_ANY, _("Cancel"));
m_cancelButton->Bind(wxEVT_BUTTON, &PairingDialog::OnCancelButton, this);
right_side->Add(m_cancelButton, 0, wxALL, 5);

rows->Add(right_side, 1, wxALIGN_RIGHT, 5);
}

sizer->Add(rows, 0, wxALL | wxEXPAND, 5);

SetSizerAndFit(sizer);
Centre(wxBOTH);

Bind(wxEVT_CLOSE_WINDOW, &PairingDialog::OnClose, this);
Bind(wxEVT_PROGRESS_PAIR, &PairingDialog::OnGaugeUpdate, this);

m_thread = std::thread(&PairingDialog::WorkerThread, this);
}

PairingDialog::~PairingDialog()
{
Unbind(wxEVT_CLOSE_WINDOW, &PairingDialog::OnClose, this);
}

void PairingDialog::OnClose(wxCloseEvent& event)
{
event.Skip();

m_threadShouldQuit = true;
if (m_thread.joinable())
m_thread.join();
}

void PairingDialog::OnCancelButton(const wxCommandEvent& event)
{
Close();
}

void PairingDialog::OnGaugeUpdate(wxCommandEvent& event)
{
PairingState state = (PairingState)event.GetInt();

switch (state)
{
case PairingState::Pairing:
{
m_text->SetLabel(_("Found controller. Pairing..."));
m_gauge->SetValue(50);
break;
}

case PairingState::Finished:
{
m_text->SetLabel(_("Successfully paired the controller."));
m_gauge->SetValue(100);
m_cancelButton->SetLabel(_("Close"));
break;
}

case PairingState::NoBluetoothAvailable:
{
m_text->SetLabel(_("Failed to find a suitable Bluetooth radio."));
m_gauge->SetValue(0);
m_cancelButton->SetLabel(_("Close"));
break;
}

case PairingState::BluetoothFailed:
{
m_text->SetLabel(_("Failed to search for controllers."));
m_gauge->SetValue(0);
m_cancelButton->SetLabel(_("Close"));
break;
}

case PairingState::PairingFailed:
{
m_text->SetLabel(_("Failed to pair with the found controller."));
m_gauge->SetValue(0);
m_cancelButton->SetLabel(_("Close"));
break;
}

default:
{
break;
}
}
}

void PairingDialog::WorkerThread()
{
const std::wstring wiimoteName = L"Nintendo RVL-CNT-01";
const std::wstring wiiUProControllerName = L"Nintendo RVL-CNT-01-UC";

#if BOOST_OS_WINDOWS
const GUID bthHidGuid = {0x00001124,0x0000,0x1000,{0x80,0x00,0x00,0x80,0x5F,0x9B,0x34,0xFB}};

const BLUETOOTH_FIND_RADIO_PARAMS radioFindParams =
{
.dwSize = sizeof(BLUETOOTH_FIND_RADIO_PARAMS)
};

HANDLE radio = INVALID_HANDLE_VALUE;
HBLUETOOTH_RADIO_FIND radioFind = BluetoothFindFirstRadio(&radioFindParams, &radio);
if (radioFind == nullptr)
{
UpdateCallback(PairingState::NoBluetoothAvailable);
return;
}

BluetoothFindRadioClose(radioFind);

BLUETOOTH_RADIO_INFO radioInfo =
{
.dwSize = sizeof(BLUETOOTH_RADIO_INFO)
};

DWORD result = BluetoothGetRadioInfo(radio, &radioInfo);
if (result != ERROR_SUCCESS)
{
UpdateCallback(PairingState::NoBluetoothAvailable);
return;
}

const BLUETOOTH_DEVICE_SEARCH_PARAMS searchParams =
{
.dwSize = sizeof(BLUETOOTH_DEVICE_SEARCH_PARAMS),

.fReturnAuthenticated = FALSE,
.fReturnRemembered = FALSE,
.fReturnUnknown = TRUE,
.fReturnConnected = FALSE,

.fIssueInquiry = TRUE,
.cTimeoutMultiplier = 5,

.hRadio = radio
};

BLUETOOTH_DEVICE_INFO info =
{
.dwSize = sizeof(BLUETOOTH_DEVICE_INFO)
};

while (!m_threadShouldQuit)
{
OutputDebugStringW(L"begin\n");

HBLUETOOTH_DEVICE_FIND deviceFind = BluetoothFindFirstDevice(&searchParams, &info);
if (deviceFind == nullptr)
{
UpdateCallback(PairingState::BluetoothFailed);
return;
}

while (!m_threadShouldQuit)
{
OutputDebugStringW(info.szName);
OutputDebugStringW(L"\n");
if (info.szName == wiimoteName || info.szName == wiiUProControllerName)
{
BluetoothFindDeviceClose(deviceFind);

UpdateCallback(PairingState::Pairing);

wchar_t passwd[6] = { radioInfo.address.rgBytes[0], radioInfo.address.rgBytes[1], radioInfo.address.rgBytes[2], radioInfo.address.rgBytes[3], radioInfo.address.rgBytes[4], radioInfo.address.rgBytes[5] };
DWORD bthResult = BluetoothAuthenticateDevice(nullptr, radio, &info, passwd, 6);
if (bthResult != ERROR_SUCCESS)
{
UpdateCallback(PairingState::PairingFailed);
return;
}

bthResult = BluetoothSetServiceState(radio, &info, &bthHidGuid, BLUETOOTH_SERVICE_ENABLE);
if (bthResult != ERROR_SUCCESS)
{
UpdateCallback(PairingState::PairingFailed);
return;
}

UpdateCallback(PairingState::Finished);
return;
}

BOOL nextDevResult = BluetoothFindNextDevice(deviceFind, &info);
if (nextDevResult == FALSE)
{
break;
}
}

BluetoothFindDeviceClose(deviceFind);
}

#else
cemuLog_log(LogType::Force, "pairing not implemented");

UpdateCallback(PairingState::NoBluetoothAvailable);
#endif
}

void PairingDialog::UpdateCallback(PairingState state)
{
auto* event = new wxCommandEvent(wxEVT_PROGRESS_PAIR);
event->SetInt((int)state);
wxQueueEvent(this, event);
}
35 changes: 35 additions & 0 deletions src/gui/PairingDialog.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#pragma once

#include <wx/collpane.h>
#include <wx/propgrid/propgrid.h>

class PairingDialog : public wxDialog
{
public:
PairingDialog(wxWindow* parent);
~PairingDialog();

private:
enum class PairingState
{
Pairing,
Finished,
NoBluetoothAvailable,
BluetoothFailed,
PairingFailed
};

void OnClose(wxCloseEvent& event);
void OnCancelButton(const wxCommandEvent& event);
void OnGaugeUpdate(wxCommandEvent& event);

void WorkerThread();
void UpdateCallback(PairingState state);

wxStaticText* m_text;
wxGauge* m_gauge;
wxButton* m_cancelButton;

std::thread m_thread;
bool m_threadShouldQuit = false;
};

0 comments on commit b2ba9c3

Please sign in to comment.