mirror of
https://github.com/mitch030504/Wiicompiled_VR_Frame.git
synced 2026-10-06 03:00:14 +02:00
WUP
This commit is contained in:
1 parent
8d569ef77b
commit
69be584cc5
19 files changed
+65
-675
No files matched your search
@@ -25,9 +25,6 @@ Code.pul
|
|||||||
# Build output
|
# Build output
|
||||||
/build/
|
/build/
|
||||||
/build-*/
|
/build-*/
|
||||||
/.build-output/
|
|
||||||
/.build-test/
|
|
||||||
/native-build/
|
|
||||||
/dist/
|
/dist/
|
||||||
/out/
|
/out/
|
||||||
[Bb]in/
|
[Bb]in/
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ Assert-Directory $dependencySources 'Pinned offline dependency sources'
|
|||||||
# to compile (launcher/Prepare-NativePrebuilt.ps1).
|
# to compile (launcher/Prepare-NativePrebuilt.ps1).
|
||||||
# Kept in step with InstalledLayout.DependencyNames by Test-PinnedFacts.ps1: the installed host
|
# Kept in step with InstalledLayout.DependencyNames by Test-PinnedFacts.ps1: the installed host
|
||||||
# refuses to call a toolkit complete unless every one of these directories is present.
|
# refuses to call a toolkit complete unless every one of these directories is present.
|
||||||
$requiredDependencies = @('abseil-cpp','cppwinrt','dawn_prebuilt','fmt','freetype','imgui','native_prebuilt','png','SDL','sqlite3','tracy','xxhash','zlib','zstd')
|
$requiredDependencies = @('abseil-cpp','cppwinrt','dawn_prebuilt','fmt','freetype','imgui','libusb','native_prebuilt','png','SDL','sqlite3','tracy','xxhash','zlib','zstd')
|
||||||
|
|
||||||
# The precompiled archives are only interchangeable with what the user's machine
|
# The precompiled archives are only interchangeable with what the user's machine
|
||||||
# compiles if both came from this toolchain and this flag set, so a stale package
|
# compiles if both came from this toolchain and this flag set, so a stale package
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ internal static class InstalledLayout
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly string[] DependencyNames =
|
public static readonly string[] DependencyNames =
|
||||||
[
|
[
|
||||||
"abseil-cpp", "cppwinrt", "dawn_prebuilt", "fmt", "freetype", "imgui", "native_prebuilt",
|
"abseil-cpp", "cppwinrt", "dawn_prebuilt", "fmt", "freetype", "imgui", "libusb", "native_prebuilt",
|
||||||
"png", "SDL", "sqlite3", "tracy", "xxhash", "zlib", "zstd"
|
"png", "SDL", "sqlite3", "tracy", "xxhash", "zlib", "zstd"
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,8 @@ The port does NOT pretend to be a Wii Remote or Classic Controller.
|
|||||||
Mappings are positional (`south`, `east`, `west`, `north`) rather than Xbox-labelled, so the
|
Mappings are positional (`south`, `east`, `west`, `north`) rather than Xbox-labelled, so the
|
||||||
same config makes sense on Xbox, PlayStation, Nintendo and generic SDL pads alike, and extra
|
same config makes sense on Xbox, PlayStation, Nintendo and generic SDL pads alike, and extra
|
||||||
inputs like paddles, touchpads and share buttons show up when the hardware reports them.
|
inputs like paddles, touchpads and share buttons show up when the hardware reports them.
|
||||||
|
The official Wii U / Switch GameCube adapter (WUP-028) works too; as with Dolphin, on Windows the
|
||||||
|
adapter must be switched to the WinUSB driver once (Zadig).
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ Source: <https://github.com/higan-emu/libco>. Full license text:
|
|||||||
|
|
||||||
## Fetched at build time and redistributed in release builds
|
## Fetched at build time and redistributed in release builds
|
||||||
|
|
||||||
These are pinned in `aurora-main/extern/CMakeLists.txt` and
|
These are pinned in `aurora-main/extern/CMakeLists.txt`, `aurora-main/CMakeLists.txt` and
|
||||||
`aurora-main/cmake/AuroraDawnProvider.cmake`. They are not stored in this repository; the build
|
`aurora-main/cmake/AuroraDawnProvider.cmake`. They are not stored in this repository; the build
|
||||||
downloads them, and release installers carry the resulting binaries. Their license texts are
|
downloads them, and release installers carry the resulting binaries. Their license texts are
|
||||||
included in the installer's `licenses/` folder.
|
included in the installer's `licenses/` folder.
|
||||||
@@ -129,6 +129,7 @@ included in the installer's `licenses/` folder.
|
|||||||
| Tint (part of Dawn) | with Dawn | BSD-3-Clause | <https://dawn.googlesource.com/dawn> |
|
| Tint (part of Dawn) | with Dawn | BSD-3-Clause | <https://dawn.googlesource.com/dawn> |
|
||||||
| DirectXShaderCompiler (`dxcompiler.dll`) | with Dawn | NCSA / University of Illinois Open Source | <https://github.com/microsoft/DirectXShaderCompiler> |
|
| DirectXShaderCompiler (`dxcompiler.dll`) | with Dawn | NCSA / University of Illinois Open Source | <https://github.com/microsoft/DirectXShaderCompiler> |
|
||||||
| SDL | 3.4.4 | zlib | <https://github.com/libsdl-org/SDL> |
|
| SDL | 3.4.4 | zlib | <https://github.com/libsdl-org/SDL> |
|
||||||
|
| libusb (linked into SDL on Windows) | 1.0.30 | LGPL-2.1-or-later | <https://github.com/libusb/libusb> |
|
||||||
| Abseil | LTS 20240722.0 | Apache-2.0 | <https://github.com/abseil/abseil-cpp> |
|
| Abseil | LTS 20240722.0 | Apache-2.0 | <https://github.com/abseil/abseil-cpp> |
|
||||||
| Dear ImGui | 1.91.9b-docking | MIT | <https://github.com/ocornut/imgui> |
|
| Dear ImGui | 1.91.9b-docking | MIT | <https://github.com/ocornut/imgui> |
|
||||||
| {fmt} | 11.1.4 | MIT | <https://github.com/fmtlib/fmt> |
|
| {fmt} | 11.1.4 | MIT | <https://github.com/fmtlib/fmt> |
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ option(AURORA_CACHE_USE_ZSTD "Compress WebGPU cache entries with zstd" ON)
|
|||||||
set(AURORA_DAWN_VERSION "v20260603.191052" CACHE STRING "Dawn version tag (https://github.com/encounter/dawn-build/releases)")
|
set(AURORA_DAWN_VERSION "v20260603.191052" CACHE STRING "Dawn version tag (https://github.com/encounter/dawn-build/releases)")
|
||||||
set(AURORA_SDL3_VERSION "3.4.4" CACHE STRING "SDL3 version tag (https://github.com/libsdl-org/SDL/releases)")
|
set(AURORA_SDL3_VERSION "3.4.4" CACHE STRING "SDL3 version tag (https://github.com/libsdl-org/SDL/releases)")
|
||||||
set(AURORA_NOD_VERSION "v2.0.0-alpha.8" CACHE STRING "nod version tag (https://github.com/encounter/nod/releases)")
|
set(AURORA_NOD_VERSION "v2.0.0-alpha.8" CACHE STRING "nod version tag (https://github.com/encounter/nod/releases)")
|
||||||
|
set(AURORA_LIBUSB_VERSION "1.0.30" CACHE STRING "libusb version tag (https://github.com/libusb/libusb/releases)")
|
||||||
|
|
||||||
# Platform-specific defaults
|
# Platform-specific defaults
|
||||||
if (CMAKE_CROSSCOMPILING)
|
if (CMAKE_CROSSCOMPILING)
|
||||||
@@ -44,6 +45,7 @@ set(AURORA_SDL3_PROVIDER "${_default_provider}" CACHE STRING
|
|||||||
set_property(CACHE AURORA_SDL3_PROVIDER PROPERTY STRINGS auto vendor system package)
|
set_property(CACHE AURORA_SDL3_PROVIDER PROPERTY STRINGS auto vendor system package)
|
||||||
set(AURORA_SDL3_LINKAGE "${_default_linkage}" CACHE STRING "SDL3 linkage type preference")
|
set(AURORA_SDL3_LINKAGE "${_default_linkage}" CACHE STRING "SDL3 linkage type preference")
|
||||||
set_property(CACHE AURORA_SDL3_LINKAGE PROPERTY STRINGS shared static)
|
set_property(CACHE AURORA_SDL3_LINKAGE PROPERTY STRINGS shared static)
|
||||||
|
option(AURORA_SDL3_LIBUSB "Build the vendored SDL3 with libusb on Windows (official GameCube adapter support)" ON)
|
||||||
|
|
||||||
# nod (if AURORA_ENABLE_DVD)
|
# nod (if AURORA_ENABLE_DVD)
|
||||||
set(AURORA_NOD_PROVIDER "${_default_provider}" CACHE STRING
|
set(AURORA_NOD_PROVIDER "${_default_provider}" CACHE STRING
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# libusb for SDL3's HIDAPI joystick drivers on Windows.
|
||||||
|
#
|
||||||
|
# The official GameCube adapter (WUP-028) is a vendor-specific USB device, not HID, so SDL3
|
||||||
|
# can only reach it through libusb - which the official SDL3 packages leave out. The vendored
|
||||||
|
# SDL3 build compiles libusb from the pinned upstream release and links it in statically.
|
||||||
|
|
||||||
|
include(FetchContent)
|
||||||
|
FetchContent_Declare(libusb
|
||||||
|
URL "https://github.com/libusb/libusb/releases/download/v${AURORA_LIBUSB_VERSION}/libusb-${AURORA_LIBUSB_VERSION}.tar.bz2"
|
||||||
|
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
||||||
|
)
|
||||||
|
# Upstream ships no CMakeLists.txt, so this only populates the source tree.
|
||||||
|
FetchContent_MakeAvailable(libusb)
|
||||||
|
|
||||||
|
set(_libusb_root "${libusb_SOURCE_DIR}/libusb")
|
||||||
|
add_library(usb-1.0 STATIC
|
||||||
|
"${_libusb_root}/core.c"
|
||||||
|
"${_libusb_root}/descriptor.c"
|
||||||
|
"${_libusb_root}/hotplug.c"
|
||||||
|
"${_libusb_root}/io.c"
|
||||||
|
"${_libusb_root}/strerror.c"
|
||||||
|
"${_libusb_root}/sync.c"
|
||||||
|
"${_libusb_root}/os/events_windows.c"
|
||||||
|
"${_libusb_root}/os/threads_windows.c"
|
||||||
|
"${_libusb_root}/os/windows_common.c"
|
||||||
|
"${_libusb_root}/os/windows_usbdk.c"
|
||||||
|
"${_libusb_root}/os/windows_winusb.c"
|
||||||
|
)
|
||||||
|
target_include_directories(usb-1.0
|
||||||
|
PUBLIC "${_libusb_root}"
|
||||||
|
PRIVATE "${CMAKE_CURRENT_LIST_DIR}/libusb" "${_libusb_root}/os"
|
||||||
|
)
|
||||||
|
set_target_properties(usb-1.0 PROPERTIES UNITY_BUILD OFF)
|
||||||
|
add_library(LibUSB::LibUSB ALIAS usb-1.0)
|
||||||
|
|
||||||
|
# SDL's FindLibUSB expects an installed copy. Satisfy its presence checks with this target
|
||||||
|
# instead: the alias pre-empts the imported target it would otherwise create, and the link
|
||||||
|
# probe is answered up front because the archive does not exist until build time.
|
||||||
|
set(LibUSB_INCLUDE_PATH "${_libusb_root}" CACHE PATH "" FORCE)
|
||||||
|
set(LibUSB_LIBRARY "usb-1.0" CACHE STRING "" FORCE)
|
||||||
|
set(HAVE_LIBUSB_H 1 CACHE INTERNAL "")
|
||||||
|
set(SDL_HIDAPI_LIBUSB ON CACHE BOOL "" FORCE)
|
||||||
|
set(SDL_HIDAPI_LIBUSB_SHARED OFF CACHE BOOL "" FORCE)
|
||||||
@@ -129,6 +129,9 @@ elseif (_aurora_sdl3_provider STREQUAL "vendor")
|
|||||||
endif ()
|
endif ()
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
set(SDL_LIBC ON CACHE BOOL "Use the system C library" FORCE)
|
set(SDL_LIBC ON CACHE BOOL "Use the system C library" FORCE)
|
||||||
|
if (AURORA_SDL3_LIBUSB)
|
||||||
|
include("${CMAKE_CURRENT_LIST_DIR}/AuroraLibUSB.cmake")
|
||||||
|
endif ()
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
include(FetchContent)
|
include(FetchContent)
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
/* libusb build configuration for the Windows backend (MinGW/Clang). */
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define PLATFORM_WINDOWS 1
|
||||||
|
#define ENABLE_LOGGING 1
|
||||||
|
#define DEFAULT_VISIBILITY
|
||||||
|
#define HAVE_STRUCT_TIMESPEC 1
|
||||||
|
#define PRINTF_FORMAT(a, b) __attribute__((__format__(__printf__, a, b)))
|
||||||
@@ -229,7 +229,6 @@ s32 PADGetNativeButtonPressed(u32 port);
|
|||||||
PADSignedNativeAxis PADGetNativeAxisPulled(u32 port);
|
PADSignedNativeAxis PADGetNativeAxisPulled(u32 port);
|
||||||
void PADRestoreDefaultMapping(u32 port);
|
void PADRestoreDefaultMapping(u32 port);
|
||||||
void PADBlockInput(bool block);
|
void PADBlockInput(bool block);
|
||||||
bool PADIsInputBlocked(void);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the default controller mapping used.
|
* Set the default controller mapping used.
|
||||||
|
|||||||
@@ -1540,7 +1540,6 @@ void PADBlockInput(const bool block) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PADIsInputBlocked() { return g_blockPAD.load(std::memory_order_acquire); }
|
|
||||||
|
|
||||||
SDL_Gamepad* PADGetSDLGamepadForIndex(const u32 index) {
|
SDL_Gamepad* PADGetSDLGamepadForIndex(const u32 index) {
|
||||||
const auto* ctrl = __PADGetControllerForIndex(index);
|
const auto* ctrl = __PADGetControllerForIndex(index);
|
||||||
|
|||||||
@@ -237,15 +237,6 @@ set(MKW_CPU_BASELINE_SOURCE "${CMAKE_CURRENT_LIST_DIR}/src/host_cpu_baseline.cpp
|
|||||||
list(REMOVE_ITEM SOURCES ${MKW_BASE_PRODUCT_SOURCE} ${MKW_RETRO_REWIND_PRODUCT_SOURCE}
|
list(REMOVE_ITEM SOURCES ${MKW_BASE_PRODUCT_SOURCE} ${MKW_RETRO_REWIND_PRODUCT_SOURCE}
|
||||||
${MKW_CPU_BASELINE_SOURCE})
|
${MKW_CPU_BASELINE_SOURCE})
|
||||||
|
|
||||||
# WUP-028 (official GameCube adapter) support talks to the adapter over WinUSB, which only
|
|
||||||
# exists on Windows - SDL3 already exposes the same hardware as a normal joystick on Linux/macOS,
|
|
||||||
# so this file has nothing to do there. Every call site into it is separately gated behind
|
|
||||||
# #if defined(_WIN32) (see wup028_adapter.cpp's own header comment), so it's safe to simply not
|
|
||||||
# compile it at all on other platforms rather than build a stub implementation.
|
|
||||||
if(NOT WIN32)
|
|
||||||
list(REMOVE_ITEM SOURCES "${CMAKE_CURRENT_LIST_DIR}/src/wup028_adapter.cpp")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# The translator emits the complete, content-addressed source graph. Consuming
|
# The translator emits the complete, content-addressed source graph. Consuming
|
||||||
# this one manifest keeps configure independent of the 28k generated function
|
# this one manifest keeps configure independent of the 28k generated function
|
||||||
# files and of optional Retro Rewind artifacts such as code.map.
|
# files and of optional Retro Rewind artifacts such as code.map.
|
||||||
|
|||||||
@@ -210,7 +210,7 @@ function(mkw_configure_product target)
|
|||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
target_link_libraries(${target} PRIVATE
|
target_link_libraries(${target} PRIVATE
|
||||||
dbghelp user32 winmm ws2_32 iphlpapi secur32 crypt32 windowsapp setupapi winusb)
|
dbghelp user32 winmm ws2_32 iphlpapi secur32 crypt32 windowsapp)
|
||||||
|
|
||||||
set_target_properties(${target} PROPERTIES WIN32_EXECUTABLE TRUE)
|
set_target_properties(${target} PROPERTIES WIN32_EXECUTABLE TRUE)
|
||||||
else()
|
else()
|
||||||
|
|||||||
@@ -63,11 +63,6 @@ struct RuntimeUserConfig {
|
|||||||
// comma-separated SDL-style physical button names ("south", or
|
// comma-separated SDL-style physical button names ("south", or
|
||||||
// "dpad_up,left_shoulder") as values; pressing either bound button counts.
|
// "dpad_up,left_shoulder") as values; pressing either bound button counts.
|
||||||
std::array<std::optional<std::string>, 12> controllerButtons;
|
std::array<std::optional<std::string>, 12> controllerButtons;
|
||||||
#ifdef _WIN32
|
|
||||||
// One-based physical WUP-028 adapter port assigned to each game port.
|
|
||||||
// Zero or a missing value means the adapter does not own that game port.
|
|
||||||
std::array<uint32_t, 4> gameCubeAdapterPorts{};
|
|
||||||
#endif
|
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace RuntimeConfigFile {
|
namespace RuntimeConfigFile {
|
||||||
@@ -367,14 +362,6 @@ inline RuntimeUserConfig ParseConfigDocument(const toml::value& document) {
|
|||||||
config.controllerButtons[index] =
|
config.controllerButtons[index] =
|
||||||
FindConfigValue<std::string>(document, "controller", buttonKeys[index]);
|
FindConfigValue<std::string>(document, "controller", buttonKeys[index]);
|
||||||
}
|
}
|
||||||
#ifdef _WIN32
|
|
||||||
for (size_t index = 0; index < config.gameCubeAdapterPorts.size(); ++index) {
|
|
||||||
const std::string key = "adapter_port_" + std::to_string(index + 1);
|
|
||||||
if (auto value = FindConfigUint(document, "controller", key); value && *value <= 4) {
|
|
||||||
config.gameCubeAdapterPorts[index] = *value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
config.widescreen = FindConfigValue<bool>(document, "video", "widescreen");
|
config.widescreen = FindConfigValue<bool>(document, "video", "widescreen");
|
||||||
config.windowPosX = FindConfigInt(document, "video", "window_x");
|
config.windowPosX = FindConfigInt(document, "video", "window_x");
|
||||||
@@ -638,21 +625,6 @@ inline bool SetControllerButton(size_t index, std::string value) {
|
|||||||
return WriteSetting("controller", kControllerButtonKeys[index], FormatString(value));
|
return WriteSetting("controller", kControllerButtonKeys[index], FormatString(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
inline int GameCubeAdapterPort(size_t gamePort) {
|
|
||||||
if (gamePort >= Get().gameCubeAdapterPorts.size()) return -1;
|
|
||||||
const uint32_t physicalPort = Get().gameCubeAdapterPorts[gamePort];
|
|
||||||
return physicalPort >= 1 && physicalPort <= 4 ? static_cast<int>(physicalPort - 1) : -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool SetGameCubeAdapterPort(size_t gamePort, int physicalPort) {
|
|
||||||
if (gamePort >= Mutable().gameCubeAdapterPorts.size() || physicalPort < -1 || physicalPort >= 4) return false;
|
|
||||||
const uint32_t storedPort = physicalPort < 0 ? 0u : static_cast<uint32_t>(physicalPort + 1);
|
|
||||||
Mutable().gameCubeAdapterPorts[gamePort] = storedPort;
|
|
||||||
return WriteSetting("controller", "adapter_port_" + std::to_string(gamePort + 1), std::to_string(storedPort));
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
inline bool SetAudioVolume(float value) {
|
inline bool SetAudioVolume(float value) {
|
||||||
value = std::clamp(value, 0.0f, 1.0f);
|
value = std::clamp(value, 0.0f, 1.0f);
|
||||||
Mutable().audioVolume = value;
|
Mutable().audioVolume = value;
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
struct PADStatus;
|
|
||||||
|
|
||||||
namespace Wup028Adapter {
|
|
||||||
|
|
||||||
enum class ConnectionState : uint8_t {
|
|
||||||
Searching,
|
|
||||||
DriverError,
|
|
||||||
Connected,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct AdapterInfo {
|
|
||||||
ConnectionState state = ConnectionState::Searching;
|
|
||||||
std::string deviceName;
|
|
||||||
std::string detail;
|
|
||||||
float pollRateHz = 0.0f;
|
|
||||||
uint8_t inputEndpoint = 0;
|
|
||||||
uint8_t outputEndpoint = 0;
|
|
||||||
std::array<bool, 4> ports{};
|
|
||||||
// Raw adapter type/status byte. High nibble 1 is wired, 2 is wireless.
|
|
||||||
std::array<uint8_t, 4> portStatus{};
|
|
||||||
std::array<uint64_t, 4> portChangeSequence{};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Starts the project-owned WUP-028 worker. Safe to call more than once.
|
|
||||||
void Initialize();
|
|
||||||
void Shutdown();
|
|
||||||
|
|
||||||
// Returns true while an official adapter is open. Each entry is a native
|
|
||||||
// GameCube port; an empty port is represented by PAD_ERR_NO_CONTROLLER.
|
|
||||||
bool Read(std::array<PADStatus, 4>& statuses);
|
|
||||||
// Assigns a physical adapter port to a game port. Pass -1 to leave the game
|
|
||||||
// port under Aurora's normal controller assignment.
|
|
||||||
void SetPortAssignment(uint32_t gamePort, int physicalPort);
|
|
||||||
int GetPortAssignment(uint32_t gamePort);
|
|
||||||
// Returns true when the command belongs to an active WUP-028, allowing the
|
|
||||||
// caller to avoid also sending it to Aurora's unrelated controller backend.
|
|
||||||
bool SetRumble(uint32_t port, bool enabled);
|
|
||||||
AdapterInfo GetInfo();
|
|
||||||
|
|
||||||
} // namespace Wup028Adapter
|
|
||||||
@@ -1,9 +1,6 @@
|
|||||||
#include "hle_stubs.h"
|
#include "hle_stubs.h"
|
||||||
#include "memory.h"
|
#include "memory.h"
|
||||||
#include "hle/controller_status_contract.h"
|
#include "hle/controller_status_contract.h"
|
||||||
#ifdef _WIN32
|
|
||||||
#include "wup028_adapter.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
@@ -36,9 +33,6 @@ void WritePadStatus(uint32_t base, const PADStatus& status) {
|
|||||||
|
|
||||||
extern "C" uint32_t PAD__Init_HLE()
|
extern "C" uint32_t PAD__Init_HLE()
|
||||||
{
|
{
|
||||||
#if defined(_WIN32)
|
|
||||||
Wup028Adapter::Initialize();
|
|
||||||
#endif
|
|
||||||
return PADInit() ? 1u : 0u;
|
return PADInit() ? 1u : 0u;
|
||||||
}
|
}
|
||||||
PPC_NATIVE_OVERRIDE(801AF2F0, PAD__Init_HLE, uint32_t, (), ());
|
PPC_NATIVE_OVERRIDE(801AF2F0, PAD__Init_HLE, uint32_t, (), ());
|
||||||
@@ -51,17 +45,6 @@ extern "C" uint32_t PAD__Read_HLE(uint32_t statusPtr)
|
|||||||
|
|
||||||
PADStatus statuses[PAD_CHANMAX]{};
|
PADStatus statuses[PAD_CHANMAX]{};
|
||||||
uint32_t rumbleMask = PADRead(statuses);
|
uint32_t rumbleMask = PADRead(statuses);
|
||||||
#if defined(_WIN32)
|
|
||||||
std::array<PADStatus, PAD_CHANMAX> adapterStatuses{};
|
|
||||||
if (Wup028Adapter::Read(adapterStatuses) && !PADIsInputBlocked()) {
|
|
||||||
for (uint32_t port = 0; port < PAD_CHANMAX; ++port) {
|
|
||||||
if (adapterStatuses[port].err == PAD_ERR_NONE) {
|
|
||||||
statuses[port] = adapterStatuses[port];
|
|
||||||
rumbleMask |= PAD_CHAN0_BIT >> port;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (uint32_t i = 0; i < PAD_CHANMAX; ++i) {
|
for (uint32_t i = 0; i < PAD_CHANMAX; ++i) {
|
||||||
@@ -90,12 +73,6 @@ PPC_NATIVE_OVERRIDE(801AF1E4, PAD__Recalibrate_HLE, uint32_t, (uint32_t mask), (
|
|||||||
|
|
||||||
extern "C" void PAD__ControlMotor_HLE(int32_t chan, uint32_t command)
|
extern "C" void PAD__ControlMotor_HLE(int32_t chan, uint32_t command)
|
||||||
{
|
{
|
||||||
#if defined(_WIN32)
|
|
||||||
if (!Wup028Adapter::SetRumble(static_cast<uint32_t>(chan), command == PAD_MOTOR_RUMBLE)) {
|
|
||||||
PADControlMotor(chan, command);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
PADControlMotor(chan, command);
|
PADControlMotor(chan, command);
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
PPC_NATIVE_OVERRIDE_VOID(801AF908, PAD__ControlMotor_HLE, (int32_t chan, uint32_t command), (chan, command));
|
PPC_NATIVE_OVERRIDE_VOID(801AF908, PAD__ControlMotor_HLE, (int32_t chan, uint32_t command), (chan, command));
|
||||||
@@ -49,7 +49,6 @@
|
|||||||
#include "system_bridge.h"
|
#include "system_bridge.h"
|
||||||
#include "ppc_runtime.h"
|
#include "ppc_runtime.h"
|
||||||
#include "aurora_events.h"
|
#include "aurora_events.h"
|
||||||
#include "wup028_adapter.h"
|
|
||||||
#include "fiber_manager.h"
|
#include "fiber_manager.h"
|
||||||
#include "hle_stubs.h"
|
#include "hle_stubs.h"
|
||||||
#include "runtime_config.h"
|
#include "runtime_config.h"
|
||||||
@@ -59,7 +58,6 @@
|
|||||||
#include <aurora/aurora.h>
|
#include <aurora/aurora.h>
|
||||||
#include <aurora/gfx.h>
|
#include <aurora/gfx.h>
|
||||||
#include <dolphin/gx/GXAurora.h>
|
#include <dolphin/gx/GXAurora.h>
|
||||||
#include <dolphin/pad.h>
|
|
||||||
#include <dolphin/vi.h>
|
#include <dolphin/vi.h>
|
||||||
|
|
||||||
// Defined in `runtime/src/hle/vi.cpp` (used by GX/VI HLE).
|
// Defined in `runtime/src/hle/vi.cpp` (used by GX/VI HLE).
|
||||||
@@ -1378,12 +1376,6 @@ int RuntimeMain(int argc, char** argv) {
|
|||||||
}
|
}
|
||||||
aurora_set_frame_worker_wait_callback(ServiceGuestTimingDuringAuroraFrameWait);
|
aurora_set_frame_worker_wait_callback(ServiceGuestTimingDuringAuroraFrameWait);
|
||||||
GxGuestWrite::InstallAuroraHooks();
|
GxGuestWrite::InstallAuroraHooks();
|
||||||
#if defined(_WIN32)
|
|
||||||
Wup028Adapter::Initialize();
|
|
||||||
for (uint32_t port = 0; port < PAD_CHANMAX; ++port) {
|
|
||||||
if (Wup028Adapter::GetPortAssignment(port) >= 0) PADClearPort(port);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
UpdateMkwDynamicAspectSurface(auroraInfo.windowSize.native_fb_width,
|
UpdateMkwDynamicAspectSurface(auroraInfo.windowSize.native_fb_width,
|
||||||
auroraInfo.windowSize.native_fb_height);
|
auroraInfo.windowSize.native_fb_height);
|
||||||
settings_overlay::InitializeRuntimeSettings();
|
settings_overlay::InitializeRuntimeSettings();
|
||||||
@@ -1425,9 +1417,6 @@ int RuntimeMain(int argc, char** argv) {
|
|||||||
// Shutdown fiber system
|
// Shutdown fiber system
|
||||||
Fiber::GuestFiberManager::Shutdown();
|
Fiber::GuestFiberManager::Shutdown();
|
||||||
WindowPlacementPersistence::Flush(true);
|
WindowPlacementPersistence::Flush(true);
|
||||||
#if defined(_WIN32)
|
|
||||||
Wup028Adapter::Shutdown();
|
|
||||||
#endif
|
|
||||||
aurora_shutdown();
|
aurora_shutdown();
|
||||||
SetRuntimeExitCodeImpl(0);
|
SetRuntimeExitCodeImpl(0);
|
||||||
ShutdownProcessTranscript();
|
ShutdownProcessTranscript();
|
||||||
@@ -1445,9 +1434,6 @@ int RuntimeMain(int argc, char** argv) {
|
|||||||
SetRuntimeExitCodeImpl(1);
|
SetRuntimeExitCodeImpl(1);
|
||||||
Fiber::GuestFiberManager::Shutdown();
|
Fiber::GuestFiberManager::Shutdown();
|
||||||
WindowPlacementPersistence::Flush(true);
|
WindowPlacementPersistence::Flush(true);
|
||||||
#if defined(_WIN32)
|
|
||||||
Wup028Adapter::Shutdown();
|
|
||||||
#endif
|
|
||||||
aurora_shutdown();
|
aurora_shutdown();
|
||||||
ShutdownProcessTranscript();
|
ShutdownProcessTranscript();
|
||||||
return 1;
|
return 1;
|
||||||
@@ -1459,9 +1445,6 @@ int RuntimeMain(int argc, char** argv) {
|
|||||||
SetRuntimeExitCodeImpl(1);
|
SetRuntimeExitCodeImpl(1);
|
||||||
Fiber::GuestFiberManager::Shutdown();
|
Fiber::GuestFiberManager::Shutdown();
|
||||||
WindowPlacementPersistence::Flush(true);
|
WindowPlacementPersistence::Flush(true);
|
||||||
#if defined(_WIN32)
|
|
||||||
Wup028Adapter::Shutdown();
|
|
||||||
#endif
|
|
||||||
aurora_shutdown();
|
aurora_shutdown();
|
||||||
ShutdownProcessTranscript();
|
ShutdownProcessTranscript();
|
||||||
return 1;
|
return 1;
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
#include "settings_overlay.h"
|
#include "settings_overlay.h"
|
||||||
#ifdef _WIN32
|
|
||||||
#include "wup028_adapter.h"
|
|
||||||
#endif
|
|
||||||
#include "audio_backend.h"
|
#include "audio_backend.h"
|
||||||
#include "controller_mapping_wizard.h"
|
#include "controller_mapping_wizard.h"
|
||||||
#include "game_graphics_options.h"
|
#include "game_graphics_options.h"
|
||||||
@@ -314,41 +311,6 @@ void ApplyConfiguredMappings() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawGameCubeAdapterInfo() {
|
|
||||||
// The official GameCube adapter is Windows-only (see wup028_adapter.cpp); on Linux/macOS,
|
|
||||||
// Wup028Adapter is a permanently-disconnected stub, and SDL3 already exposes the same
|
|
||||||
// hardware as a normal joystick, so this menu would only ever show "Searching" and four
|
|
||||||
// perpetually-empty adapter ports - confusing clutter for a feature that can't do anything
|
|
||||||
// on this platform. Skip it entirely rather than render a menu that never has content.
|
|
||||||
#if defined(_WIN32)
|
|
||||||
ImGui::Separator();
|
|
||||||
if (!ImGui::BeginMenu("GameCube adapter info")) return;
|
|
||||||
|
|
||||||
const auto adapter = Wup028Adapter::GetInfo();
|
|
||||||
const char* state = adapter.state == Wup028Adapter::ConnectionState::Connected
|
|
||||||
? "Connected"
|
|
||||||
: adapter.state == Wup028Adapter::ConnectionState::DriverError ? "Driver error"
|
|
||||||
: "Searching";
|
|
||||||
ImGui::Text("Status: %s", state);
|
|
||||||
if (!adapter.deviceName.empty()) {
|
|
||||||
ImGui::Text("Device: %s", adapter.deviceName.c_str());
|
|
||||||
}
|
|
||||||
ImGui::TextWrapped("%s", adapter.detail.c_str());
|
|
||||||
if (adapter.state == Wup028Adapter::ConnectionState::Connected) {
|
|
||||||
ImGui::Text("Poll rate: %.1f reports/s", adapter.pollRateHz);
|
|
||||||
ImGui::Text("Endpoints: IN 0x%02X, OUT 0x%02X", adapter.inputEndpoint, adapter.outputEndpoint);
|
|
||||||
for (size_t port = 0; port < adapter.ports.size(); ++port) {
|
|
||||||
const uint8_t type = adapter.portStatus[port] & 0x30;
|
|
||||||
const char* typeName = type == 0x10 ? "wired" : type == 0x20 ? "wireless" : "none";
|
|
||||||
ImGui::Text("Adapter port %u: %s (type %s, raw 0x%02X)", static_cast<unsigned>(port + 1),
|
|
||||||
adapter.ports[port] ? "Controller connected" : "Empty", typeName,
|
|
||||||
adapter.portStatus[port]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ImGui::EndMenu();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void DrawControllerSettings() {
|
void DrawControllerSettings() {
|
||||||
for (int port = 0; port < PAD_MAX_CONTROLLERS; ++port) {
|
for (int port = 0; port < PAD_MAX_CONTROLLERS; ++port) {
|
||||||
const std::string label = "Port " + std::to_string(port + 1);
|
const std::string label = "Port " + std::to_string(port + 1);
|
||||||
@@ -360,50 +322,10 @@ void DrawControllerSettings() {
|
|||||||
|
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
const uint32_t selectedGamePort = static_cast<uint32_t>(g_controllerPort);
|
const uint32_t selectedGamePort = static_cast<uint32_t>(g_controllerPort);
|
||||||
#if defined(_WIN32)
|
const char* currentName = PADGetName(selectedGamePort);
|
||||||
const int adapterAssignment = Wup028Adapter::GetPortAssignment(selectedGamePort);
|
ImGui::Text("Assigned: %s", currentName != nullptr ? currentName : "None");
|
||||||
if (adapterAssignment >= 0) {
|
|
||||||
ImGui::Text("Assigned: GameCube adapter port %d", adapterAssignment + 1);
|
|
||||||
} else
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
const char* currentName = PADGetName(selectedGamePort);
|
|
||||||
ImGui::Text("Assigned: %s", currentName != nullptr ? currentName : "None");
|
|
||||||
}
|
|
||||||
#if defined(_WIN32)
|
|
||||||
// Windows-only, same reasoning as DrawGameCubeAdapterInfo() above: on other platforms
|
|
||||||
// adapterAssignment is always -1 and every port would always read "(empty)", so this submenu
|
|
||||||
// would never have anything real to offer.
|
|
||||||
if (ImGui::BeginMenu("Assign GameCube adapter port")) {
|
|
||||||
if (ImGui::MenuItem("None", nullptr, adapterAssignment < 0)) {
|
|
||||||
Wup028Adapter::SetPortAssignment(selectedGamePort, -1);
|
|
||||||
RuntimeConfigFile::SetGameCubeAdapterPort(selectedGamePort, -1);
|
|
||||||
}
|
|
||||||
const auto adapter = Wup028Adapter::GetInfo();
|
|
||||||
for (int physicalPort = 0; physicalPort < PAD_CHANMAX; ++physicalPort) {
|
|
||||||
const std::string label = "Adapter port " + std::to_string(physicalPort + 1) +
|
|
||||||
(adapter.ports[static_cast<size_t>(physicalPort)] ? " (connected)" : " (empty)");
|
|
||||||
if (ImGui::MenuItem(label.c_str(), nullptr, adapterAssignment == physicalPort)) {
|
|
||||||
for (uint32_t gamePort = 0; gamePort < PAD_CHANMAX; ++gamePort) {
|
|
||||||
if (gamePort != selectedGamePort && Wup028Adapter::GetPortAssignment(gamePort) == physicalPort) {
|
|
||||||
RuntimeConfigFile::SetGameCubeAdapterPort(gamePort, -1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PADClearPort(selectedGamePort);
|
|
||||||
Wup028Adapter::SetPortAssignment(selectedGamePort, physicalPort);
|
|
||||||
RuntimeConfigFile::SetGameCubeAdapterPort(selectedGamePort, physicalPort);
|
|
||||||
g_configuredControllerIndices.fill(std::numeric_limits<int32_t>::min());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ImGui::EndMenu();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
if (ImGui::MenuItem("Unassign controller")) {
|
if (ImGui::MenuItem("Unassign controller")) {
|
||||||
PADClearPort(selectedGamePort);
|
PADClearPort(selectedGamePort);
|
||||||
#if defined(_WIN32)
|
|
||||||
Wup028Adapter::SetPortAssignment(selectedGamePort, -1);
|
|
||||||
RuntimeConfigFile::SetGameCubeAdapterPort(selectedGamePort, -1);
|
|
||||||
#endif
|
|
||||||
g_configuredControllerIndices.fill(std::numeric_limits<int32_t>::min());
|
g_configuredControllerIndices.fill(std::numeric_limits<int32_t>::min());
|
||||||
}
|
}
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
@@ -411,9 +333,6 @@ void DrawControllerSettings() {
|
|||||||
const uint32_t controllerCount = PADCount();
|
const uint32_t controllerCount = PADCount();
|
||||||
if (controllerCount == 0) {
|
if (controllerCount == 0) {
|
||||||
ImGui::TextDisabled("No controller connected");
|
ImGui::TextDisabled("No controller connected");
|
||||||
#if defined(_WIN32)
|
|
||||||
DrawGameCubeAdapterInfo();
|
|
||||||
#endif
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -422,10 +341,6 @@ void DrawControllerSettings() {
|
|||||||
const char* name = PADGetNameForControllerIndex(index);
|
const char* name = PADGetNameForControllerIndex(index);
|
||||||
ImGui::PushID(static_cast<int>(index));
|
ImGui::PushID(static_cast<int>(index));
|
||||||
if (ImGui::MenuItem(name != nullptr ? name : "Unknown controller")) {
|
if (ImGui::MenuItem(name != nullptr ? name : "Unknown controller")) {
|
||||||
#if defined(_WIN32)
|
|
||||||
Wup028Adapter::SetPortAssignment(selectedGamePort, -1);
|
|
||||||
RuntimeConfigFile::SetGameCubeAdapterPort(selectedGamePort, -1);
|
|
||||||
#endif
|
|
||||||
PADSetPortForIndex(index, selectedGamePort);
|
PADSetPortForIndex(index, selectedGamePort);
|
||||||
g_configuredControllerIndices.fill(std::numeric_limits<int32_t>::min());
|
g_configuredControllerIndices.fill(std::numeric_limits<int32_t>::min());
|
||||||
ApplyConfiguredMappings();
|
ApplyConfiguredMappings();
|
||||||
@@ -439,9 +354,6 @@ void DrawControllerSettings() {
|
|||||||
PADButtonMapping* mappings = PADGetButtonMappings(static_cast<uint32_t>(g_controllerPort), &mappingCount);
|
PADButtonMapping* mappings = PADGetButtonMappings(static_cast<uint32_t>(g_controllerPort), &mappingCount);
|
||||||
if (mappings == nullptr || mappingCount != PAD_BUTTON_COUNT) {
|
if (mappings == nullptr || mappingCount != PAD_BUTTON_COUNT) {
|
||||||
ImGui::TextDisabled("Assign a controller to edit its buttons");
|
ImGui::TextDisabled("Assign a controller to edit its buttons");
|
||||||
#if defined(_WIN32)
|
|
||||||
DrawGameCubeAdapterInfo();
|
|
||||||
#endif
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -581,9 +493,6 @@ void DrawControllerSettings() {
|
|||||||
ImGui::TextUnformatted(kControllerButtons[i].label);
|
ImGui::TextUnformatted(kControllerButtons[i].label);
|
||||||
ImGui::PopID();
|
ImGui::PopID();
|
||||||
}
|
}
|
||||||
#if defined(_WIN32)
|
|
||||||
DrawGameCubeAdapterInfo();
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawAudioSettings() {
|
void DrawAudioSettings() {
|
||||||
|
|||||||
@@ -1,450 +0,0 @@
|
|||||||
#include "wup028_adapter.h"
|
|
||||||
|
|
||||||
#include "runtime_log.h"
|
|
||||||
#include "runtime_config.h"
|
|
||||||
|
|
||||||
#include <dolphin/pad.h>
|
|
||||||
#include <windows.h>
|
|
||||||
#include <initguid.h>
|
|
||||||
#include <setupapi.h>
|
|
||||||
#include <usb.h>
|
|
||||||
#include <usbiodef.h>
|
|
||||||
#include <winusb.h>
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <array>
|
|
||||||
#include <atomic>
|
|
||||||
#include <chrono>
|
|
||||||
#include <cwctype>
|
|
||||||
#include <cstring>
|
|
||||||
#include <mutex>
|
|
||||||
#include <string>
|
|
||||||
#include <thread>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace Wup028Adapter {
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
constexpr uint16_t kNintendoVendor = 0x057e;
|
|
||||||
constexpr uint16_t kAdapterProduct = 0x0337;
|
|
||||||
constexpr size_t kReportSize = 37;
|
|
||||||
constexpr auto kInputReportTimeout = std::chrono::milliseconds(500);
|
|
||||||
|
|
||||||
std::mutex g_mutex;
|
|
||||||
std::array<PADStatus, PAD_CHANMAX> g_statuses{};
|
|
||||||
std::array<uint8_t, PAD_CHANMAX> g_rumble{};
|
|
||||||
std::array<int8_t, PAD_CHANMAX> g_portAssignments{{-1, -1, -1, -1}};
|
|
||||||
std::thread g_worker;
|
|
||||||
std::atomic_bool g_stop{false};
|
|
||||||
std::atomic_bool g_running{false};
|
|
||||||
std::atomic_bool g_connected{false};
|
|
||||||
AdapterInfo g_info;
|
|
||||||
|
|
||||||
struct Device {
|
|
||||||
HANDLE file = INVALID_HANDLE_VALUE;
|
|
||||||
HANDLE event = nullptr;
|
|
||||||
WINUSB_INTERFACE_HANDLE usb = nullptr;
|
|
||||||
UCHAR inputPipe = 0;
|
|
||||||
UCHAR outputPipe = 0;
|
|
||||||
|
|
||||||
~Device() { Close(); }
|
|
||||||
void Close() {
|
|
||||||
if (usb != nullptr) WinUsb_Free(usb);
|
|
||||||
if (event != nullptr) CloseHandle(event);
|
|
||||||
if (file != INVALID_HANDLE_VALUE) CloseHandle(file);
|
|
||||||
usb = nullptr;
|
|
||||||
event = nullptr;
|
|
||||||
file = INVALID_HANDLE_VALUE;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
bool Transfer(Device& device, bool input, UCHAR pipe, UCHAR* data, ULONG size, ULONG& transferred,
|
|
||||||
DWORD timeoutMs, bool* timedOut = nullptr) {
|
|
||||||
if (timedOut != nullptr) *timedOut = false;
|
|
||||||
ResetEvent(device.event);
|
|
||||||
OVERLAPPED operation{};
|
|
||||||
operation.hEvent = device.event;
|
|
||||||
const BOOL started = input ? WinUsb_ReadPipe(device.usb, pipe, data, size, &transferred, &operation)
|
|
||||||
: WinUsb_WritePipe(device.usb, pipe, data, size, &transferred, &operation);
|
|
||||||
if (started) return true;
|
|
||||||
if (GetLastError() != ERROR_IO_PENDING) return false;
|
|
||||||
const DWORD wait = WaitForSingleObject(device.event, timeoutMs);
|
|
||||||
if (wait == WAIT_OBJECT_0) {
|
|
||||||
return WinUsb_GetOverlappedResult(device.usb, &operation, &transferred, FALSE);
|
|
||||||
}
|
|
||||||
CancelIoEx(device.file, &operation);
|
|
||||||
WaitForSingleObject(device.event, INFINITE);
|
|
||||||
WinUsb_GetOverlappedResult(device.usb, &operation, &transferred, FALSE);
|
|
||||||
if (timedOut != nullptr && wait == WAIT_TIMEOUT) *timedOut = true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct DeviceMatch {
|
|
||||||
std::wstring path;
|
|
||||||
std::string name;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::string WideToUtf8(const wchar_t* value) {
|
|
||||||
if (value == nullptr || *value == L'\0') return {};
|
|
||||||
const int size = WideCharToMultiByte(CP_UTF8, 0, value, -1, nullptr, 0, nullptr, nullptr);
|
|
||||||
if (size <= 1) return {};
|
|
||||||
std::string result(static_cast<size_t>(size), '\0');
|
|
||||||
WideCharToMultiByte(CP_UTF8, 0, value, -1, result.data(), size, nullptr, nullptr);
|
|
||||||
result.resize(static_cast<size_t>(size - 1));
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
DeviceMatch FindAdapter() {
|
|
||||||
HDEVINFO devices = SetupDiGetClassDevsW(&GUID_DEVINTERFACE_USB_DEVICE, nullptr, nullptr,
|
|
||||||
DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
|
|
||||||
if (devices == INVALID_HANDLE_VALUE) return {};
|
|
||||||
|
|
||||||
DeviceMatch result;
|
|
||||||
for (DWORD index = 0;; ++index) {
|
|
||||||
SP_DEVICE_INTERFACE_DATA iface{sizeof(iface)};
|
|
||||||
if (!SetupDiEnumDeviceInterfaces(devices, nullptr, &GUID_DEVINTERFACE_USB_DEVICE, index, &iface)) break;
|
|
||||||
|
|
||||||
DWORD required = 0;
|
|
||||||
SetupDiGetDeviceInterfaceDetailW(devices, &iface, nullptr, 0, &required, nullptr);
|
|
||||||
if (required < sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_W)) continue;
|
|
||||||
std::vector<uint8_t> storage(required);
|
|
||||||
auto* detail = reinterpret_cast<SP_DEVICE_INTERFACE_DETAIL_DATA_W*>(storage.data());
|
|
||||||
detail->cbSize = sizeof(*detail);
|
|
||||||
SP_DEVINFO_DATA deviceInfo{sizeof(deviceInfo)};
|
|
||||||
if (!SetupDiGetDeviceInterfaceDetailW(devices, &iface, detail, required, nullptr, &deviceInfo)) continue;
|
|
||||||
|
|
||||||
std::wstring lower(detail->DevicePath);
|
|
||||||
std::transform(lower.begin(), lower.end(), lower.begin(),
|
|
||||||
[](wchar_t c) { return static_cast<wchar_t>(std::towlower(c)); });
|
|
||||||
if (lower.find(L"vid_057e") != std::wstring::npos && lower.find(L"pid_0337") != std::wstring::npos) {
|
|
||||||
result.path = detail->DevicePath;
|
|
||||||
std::array<wchar_t, 256> description{};
|
|
||||||
if (SetupDiGetDeviceRegistryPropertyW(devices, &deviceInfo, SPDRP_FRIENDLYNAME, nullptr,
|
|
||||||
reinterpret_cast<BYTE*>(description.data()),
|
|
||||||
static_cast<DWORD>(description.size() * sizeof(wchar_t)), nullptr) ||
|
|
||||||
SetupDiGetDeviceRegistryPropertyW(devices, &deviceInfo, SPDRP_DEVICEDESC, nullptr,
|
|
||||||
reinterpret_cast<BYTE*>(description.data()),
|
|
||||||
static_cast<DWORD>(description.size() * sizeof(wchar_t)), nullptr)) {
|
|
||||||
result.name = WideToUtf8(description.data());
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SetupDiDestroyDeviceInfoList(devices);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Open(Device& device, std::string& name, std::string& error) {
|
|
||||||
const auto match = FindAdapter();
|
|
||||||
if (match.path.empty()) {
|
|
||||||
error = "No VID 057E / PID 0337 adapter is present";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
name = match.name.empty() ? "WUP-028-compatible adapter" : match.name;
|
|
||||||
device.file = CreateFileW(match.path.c_str(), GENERIC_READ | GENERIC_WRITE,
|
|
||||||
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING,
|
|
||||||
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, nullptr);
|
|
||||||
if (device.file == INVALID_HANDLE_VALUE || !WinUsb_Initialize(device.file, &device.usb)) {
|
|
||||||
error = "WinUSB could not open the adapter (Windows error " + std::to_string(GetLastError()) + ")";
|
|
||||||
device.Close();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
device.event = CreateEventW(nullptr, TRUE, FALSE, nullptr);
|
|
||||||
if (device.event == nullptr) {
|
|
||||||
error = "Could not create the WinUSB transfer event";
|
|
||||||
device.Close();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
USB_INTERFACE_DESCRIPTOR descriptor{};
|
|
||||||
if (!WinUsb_QueryInterfaceSettings(device.usb, 0, &descriptor)) {
|
|
||||||
error = "WinUSB could not query the adapter interface";
|
|
||||||
device.Close();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (UCHAR index = 0; index < descriptor.bNumEndpoints; ++index) {
|
|
||||||
WINUSB_PIPE_INFORMATION pipe{};
|
|
||||||
if (!WinUsb_QueryPipe(device.usb, 0, index, &pipe)) continue;
|
|
||||||
if ((pipe.PipeType == UsbdPipeTypeInterrupt || pipe.PipeType == UsbdPipeTypeBulk) &&
|
|
||||||
USB_ENDPOINT_DIRECTION_IN(pipe.PipeId) && device.inputPipe == 0) {
|
|
||||||
device.inputPipe = pipe.PipeId;
|
|
||||||
} else if ((pipe.PipeType == UsbdPipeTypeInterrupt || pipe.PipeType == UsbdPipeTypeBulk) &&
|
|
||||||
USB_ENDPOINT_DIRECTION_OUT(pipe.PipeId) && device.outputPipe == 0) {
|
|
||||||
device.outputPipe = pipe.PipeId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (device.inputPipe == 0 || device.outputPipe == 0) {
|
|
||||||
error = "The adapter has no usable input/output endpoint";
|
|
||||||
device.Close();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
UCHAR command = 0x13; // Enable the adapter's 37-byte input stream.
|
|
||||||
ULONG written = 0;
|
|
||||||
if (!Transfer(device, false, device.outputPipe, &command, 1, written, 1000) || written != 1) {
|
|
||||||
error = "The adapter rejected input initialization on endpoint 0x";
|
|
||||||
const char hex[] = "0123456789ABCDEF";
|
|
||||||
error += hex[device.outputPipe >> 4];
|
|
||||||
error += hex[device.outputPipe & 15];
|
|
||||||
device.Close();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int8_t Axis(uint8_t raw) {
|
|
||||||
constexpr int kCenter = 128;
|
|
||||||
constexpr int kCenterTolerance = 10;
|
|
||||||
if (raw >= kCenter - kCenterTolerance && raw <= kCenter + kCenterTolerance) return 0;
|
|
||||||
return static_cast<int8_t>(std::clamp(static_cast<int>(raw) - kCenter, -128, 127));
|
|
||||||
}
|
|
||||||
|
|
||||||
PADStatus DecodePort(const uint8_t* p) {
|
|
||||||
PADStatus out{};
|
|
||||||
// The WUP-028 protocol uses type 1 for wired pads and type 2 for
|
|
||||||
// WaveBird/wireless receivers. Testing only bit 0x10 drops every type-2
|
|
||||||
// controller and makes otherwise valid ports appear empty.
|
|
||||||
if ((p[0] & 0x30) == 0) {
|
|
||||||
out.err = PAD_ERR_NO_CONTROLLER;
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
if (p[1] & 0x01) out.button |= PAD_BUTTON_A;
|
|
||||||
if (p[1] & 0x02) out.button |= PAD_BUTTON_B;
|
|
||||||
if (p[1] & 0x04) out.button |= PAD_BUTTON_X;
|
|
||||||
if (p[1] & 0x08) out.button |= PAD_BUTTON_Y;
|
|
||||||
if (p[1] & 0x10) out.button |= PAD_BUTTON_LEFT;
|
|
||||||
if (p[1] & 0x20) out.button |= PAD_BUTTON_RIGHT;
|
|
||||||
if (p[1] & 0x40) out.button |= PAD_BUTTON_DOWN;
|
|
||||||
if (p[1] & 0x80) out.button |= PAD_BUTTON_UP;
|
|
||||||
if (p[2] & 0x01) out.button |= PAD_BUTTON_START;
|
|
||||||
if (p[2] & 0x02) out.button |= PAD_TRIGGER_Z;
|
|
||||||
if (p[2] & 0x04) out.button |= PAD_TRIGGER_R;
|
|
||||||
if (p[2] & 0x08) out.button |= PAD_TRIGGER_L;
|
|
||||||
out.stickX = Axis(p[3]);
|
|
||||||
out.stickY = Axis(p[4]);
|
|
||||||
out.substickX = Axis(p[5]);
|
|
||||||
out.substickY = Axis(p[6]);
|
|
||||||
out.triggerL = p[7];
|
|
||||||
out.triggerR = p[8];
|
|
||||||
out.err = PAD_ERR_NONE;
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SendRumble(Device& device, const std::array<uint8_t, PAD_CHANMAX>& motors) {
|
|
||||||
std::array<UCHAR, 5> report{{0x11, motors[0], motors[1], motors[2], motors[3]}};
|
|
||||||
ULONG written = 0;
|
|
||||||
return Transfer(device, false, device.outputPipe, report.data(), report.size(), written, 1000) &&
|
|
||||||
written == report.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RefreshInputStream(Device& device) {
|
|
||||||
UCHAR command = 0x13;
|
|
||||||
ULONG written = 0;
|
|
||||||
return Transfer(device, false, device.outputPipe, &command, 1, written, 1000) && written == 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ClearConnectedPorts(const char* reason) {
|
|
||||||
std::lock_guard lock(g_mutex);
|
|
||||||
g_rumble.fill(0);
|
|
||||||
for (size_t port = 0; port < g_info.ports.size(); ++port) {
|
|
||||||
if (g_info.ports[port]) {
|
|
||||||
g_info.ports[port] = false;
|
|
||||||
++g_info.portChangeSequence[port];
|
|
||||||
RT_LOG(RT_TAG_RUNTIME) << "GameCube adapter port " << (port + 1)
|
|
||||||
<< " controller disconnected (" << reason << ")" << std::endl;
|
|
||||||
}
|
|
||||||
g_statuses[port] = {};
|
|
||||||
g_statuses[port].err = PAD_ERR_NO_CONTROLLER;
|
|
||||||
g_info.portStatus[port] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Worker() {
|
|
||||||
std::string lastError;
|
|
||||||
while (!g_stop.load(std::memory_order_acquire)) {
|
|
||||||
Device device;
|
|
||||||
std::string name;
|
|
||||||
std::string error;
|
|
||||||
if (!Open(device, name, error)) {
|
|
||||||
g_connected.store(false, std::memory_order_release);
|
|
||||||
{
|
|
||||||
std::lock_guard lock(g_mutex);
|
|
||||||
g_info.state = error.starts_with("No VID") ? ConnectionState::Searching : ConnectionState::DriverError;
|
|
||||||
g_info.deviceName = name;
|
|
||||||
g_info.detail = error;
|
|
||||||
g_info.pollRateHz = 0.0f;
|
|
||||||
g_info.inputEndpoint = 0;
|
|
||||||
g_info.outputEndpoint = 0;
|
|
||||||
g_info.ports.fill(false);
|
|
||||||
g_info.portStatus.fill(0);
|
|
||||||
}
|
|
||||||
if (error != lastError && !error.starts_with("No VID")) {
|
|
||||||
RT_LOG(RT_TAG_RUNTIME) << "GameCube adapter: " << error << std::endl;
|
|
||||||
}
|
|
||||||
lastError = error;
|
|
||||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
RT_LOG(RT_TAG_RUNTIME) << name << " connected (input endpoint 0x" << std::hex
|
|
||||||
<< static_cast<unsigned>(device.inputPipe) << ", output endpoint 0x"
|
|
||||||
<< static_cast<unsigned>(device.outputPipe) << std::dec << ")" << std::endl;
|
|
||||||
lastError.clear();
|
|
||||||
g_connected.store(true, std::memory_order_release);
|
|
||||||
{
|
|
||||||
std::lock_guard lock(g_mutex);
|
|
||||||
g_info.state = ConnectionState::Connected;
|
|
||||||
g_info.deviceName = name;
|
|
||||||
g_info.detail = "Receiving native GameCube reports";
|
|
||||||
g_info.inputEndpoint = device.inputPipe;
|
|
||||||
g_info.outputEndpoint = device.outputPipe;
|
|
||||||
}
|
|
||||||
std::array<uint8_t, PAD_CHANMAX> sentRumble{};
|
|
||||||
auto rateStart = std::chrono::steady_clock::now();
|
|
||||||
uint32_t rateReports = 0;
|
|
||||||
std::array<bool, PAD_CHANMAX> reportedPorts{};
|
|
||||||
auto lastReport = std::chrono::steady_clock::now();
|
|
||||||
|
|
||||||
while (!g_stop.load(std::memory_order_acquire)) {
|
|
||||||
std::array<UCHAR, kReportSize> report{};
|
|
||||||
ULONG read = 0;
|
|
||||||
bool timedOut = false;
|
|
||||||
if (!Transfer(device, true, device.inputPipe, report.data(), report.size(), read, 100, &timedOut)) {
|
|
||||||
if (timedOut && std::chrono::steady_clock::now() - lastReport < kInputReportTimeout) continue;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (read != report.size() || report[0] != 0x21) {
|
|
||||||
if (std::chrono::steady_clock::now() - lastReport >= kInputReportTimeout) break;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
lastReport = std::chrono::steady_clock::now();
|
|
||||||
++rateReports;
|
|
||||||
std::array<PADStatus, PAD_CHANMAX> decoded{};
|
|
||||||
for (size_t port = 0; port < decoded.size(); ++port) decoded[port] = DecodePort(report.data() + 1 + port * 9);
|
|
||||||
std::array<uint8_t, PAD_CHANMAX> desired{};
|
|
||||||
std::array<int8_t, PAD_CHANMAX> transitions{};
|
|
||||||
bool refreshStream = false;
|
|
||||||
{
|
|
||||||
std::lock_guard lock(g_mutex);
|
|
||||||
g_statuses = decoded;
|
|
||||||
desired = g_rumble;
|
|
||||||
for (size_t port = 0; port < decoded.size(); ++port) {
|
|
||||||
g_info.portStatus[port] = report[1 + port * 9];
|
|
||||||
const bool present = decoded[port].err == PAD_ERR_NONE;
|
|
||||||
if (present != reportedPorts[port]) {
|
|
||||||
reportedPorts[port] = present;
|
|
||||||
g_info.ports[port] = present;
|
|
||||||
++g_info.portChangeSequence[port];
|
|
||||||
transitions[port] = present ? 1 : -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const auto now = std::chrono::steady_clock::now();
|
|
||||||
const float seconds = std::chrono::duration<float>(now - rateStart).count();
|
|
||||||
if (seconds >= 1.0f) {
|
|
||||||
g_info.pollRateHz = static_cast<float>(rateReports) / seconds;
|
|
||||||
rateReports = 0;
|
|
||||||
rateStart = now;
|
|
||||||
refreshStream = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (size_t port = 0; port < transitions.size(); ++port) {
|
|
||||||
if (transitions[port] != 0) {
|
|
||||||
RT_LOG(RT_TAG_RUNTIME) << "GameCube adapter port " << (port + 1) << " controller "
|
|
||||||
<< (transitions[port] > 0 ? "connected" : "disconnected") << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (desired != sentRumble) {
|
|
||||||
if (!SendRumble(device, desired)) break;
|
|
||||||
sentRumble = desired;
|
|
||||||
}
|
|
||||||
if (refreshStream && !RefreshInputStream(device)) break;
|
|
||||||
}
|
|
||||||
// Do not leave a motor latched on when stopping or abandoning this handle.
|
|
||||||
SendRumble(device, {});
|
|
||||||
g_connected.store(false, std::memory_order_release);
|
|
||||||
ClearConnectedPorts("adapter unavailable");
|
|
||||||
{
|
|
||||||
std::lock_guard lock(g_mutex);
|
|
||||||
g_info.state = ConnectionState::Searching;
|
|
||||||
g_info.detail = "Adapter disconnected; waiting for reconnect";
|
|
||||||
g_info.pollRateHz = 0.0f;
|
|
||||||
}
|
|
||||||
if (!g_stop.load(std::memory_order_acquire)) {
|
|
||||||
RT_LOG(RT_TAG_RUNTIME) << "WUP-028 GameCube adapter disconnected; waiting for reconnect" << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
void Initialize() {
|
|
||||||
bool expected = false;
|
|
||||||
if (!g_running.compare_exchange_strong(expected, true)) return;
|
|
||||||
for (auto& status : g_statuses) status.err = PAD_ERR_NO_CONTROLLER;
|
|
||||||
for (size_t gamePort = 0; gamePort < g_portAssignments.size(); ++gamePort) {
|
|
||||||
g_portAssignments[gamePort] = static_cast<int8_t>(RuntimeConfigFile::GameCubeAdapterPort(gamePort));
|
|
||||||
}
|
|
||||||
g_stop.store(false, std::memory_order_release);
|
|
||||||
g_worker = std::thread(Worker);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Shutdown() {
|
|
||||||
if (!g_running.exchange(false)) return;
|
|
||||||
g_stop.store(true, std::memory_order_release);
|
|
||||||
if (g_worker.joinable()) g_worker.join();
|
|
||||||
g_connected.store(false, std::memory_order_release);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Read(std::array<PADStatus, 4>& statuses) {
|
|
||||||
if (!g_connected.load(std::memory_order_acquire)) return false;
|
|
||||||
std::lock_guard lock(g_mutex);
|
|
||||||
for (auto& status : statuses) status.err = PAD_ERR_NO_CONTROLLER;
|
|
||||||
for (size_t gamePort = 0; gamePort < statuses.size(); ++gamePort) {
|
|
||||||
const int physicalPort = g_portAssignments[gamePort];
|
|
||||||
if (physicalPort >= 0) statuses[gamePort] = g_statuses[static_cast<size_t>(physicalPort)];
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetPortAssignment(uint32_t gamePort, int physicalPort) {
|
|
||||||
if (gamePort >= g_portAssignments.size() || physicalPort < -1 || physicalPort >= PAD_CHANMAX) return;
|
|
||||||
std::lock_guard lock(g_mutex);
|
|
||||||
const int oldPhysicalPort = g_portAssignments[gamePort];
|
|
||||||
if (oldPhysicalPort >= 0) g_rumble[static_cast<size_t>(oldPhysicalPort)] = 0;
|
|
||||||
if (physicalPort >= 0) {
|
|
||||||
for (auto& assignment : g_portAssignments) {
|
|
||||||
if (assignment == physicalPort) {
|
|
||||||
assignment = -1;
|
|
||||||
g_rumble[static_cast<size_t>(physicalPort)] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
g_portAssignments[gamePort] = static_cast<int8_t>(physicalPort);
|
|
||||||
}
|
|
||||||
|
|
||||||
int GetPortAssignment(uint32_t gamePort) {
|
|
||||||
if (gamePort >= g_portAssignments.size()) return -1;
|
|
||||||
std::lock_guard lock(g_mutex);
|
|
||||||
return g_portAssignments[gamePort];
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SetRumble(uint32_t port, bool enabled) {
|
|
||||||
if (port >= g_rumble.size() || !g_connected.load(std::memory_order_acquire)) return false;
|
|
||||||
std::lock_guard lock(g_mutex);
|
|
||||||
if (!g_connected.load(std::memory_order_acquire)) return false;
|
|
||||||
const int physicalPort = g_portAssignments[port];
|
|
||||||
if (physicalPort < 0) return false;
|
|
||||||
const size_t adapterPort = static_cast<size_t>(physicalPort);
|
|
||||||
if (g_statuses[adapterPort].err != PAD_ERR_NONE) {
|
|
||||||
g_rumble[adapterPort] = 0;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
g_rumble[adapterPort] = enabled ? 1 : 0;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
AdapterInfo GetInfo() {
|
|
||||||
std::lock_guard lock(g_mutex);
|
|
||||||
return g_info;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Wup028Adapter
|
|
||||||
Reference in new issue
Block a user