diff options
| -rw-r--r-- | CMakeLists.txt | 7 | ||||
| -rw-r--r-- | src/CMakeLists.txt | 9 | ||||
| -rw-r--r-- | src/common/thread.h | 9 | ||||
| -rw-r--r-- | src/core/hle/service/time/time_zone_manager.cpp | 9 | ||||
| -rw-r--r-- | src/core/hle/service/time/time_zone_manager.h | 1 | ||||
| -rw-r--r-- | src/core/hle/service/time/time_zone_service.cpp | 24 | ||||
| -rw-r--r-- | src/core/hle/service/time/time_zone_service.h | 1 | ||||
| -rw-r--r-- | src/core/settings.h | 3 | ||||
| -rw-r--r-- | src/input_common/CMakeLists.txt | 8 | ||||
| -rw-r--r-- | src/input_common/main.cpp | 12 | ||||
| -rw-r--r-- | src/input_common/udp/client.cpp | 287 | ||||
| -rw-r--r-- | src/input_common/udp/client.h | 96 | ||||
| -rw-r--r-- | src/input_common/udp/protocol.cpp | 79 | ||||
| -rw-r--r-- | src/input_common/udp/protocol.h | 256 | ||||
| -rw-r--r-- | src/input_common/udp/udp.cpp | 96 | ||||
| -rw-r--r-- | src/input_common/udp/udp.h | 27 | ||||
| -rw-r--r-- | src/video_core/renderer_opengl/gl_shader_cache.cpp | 4 | ||||
| -rw-r--r-- | src/yuzu/configuration/config.cpp | 17 | ||||
| -rw-r--r-- | src/yuzu_cmd/config.cpp | 5 | ||||
| -rw-r--r-- | src/yuzu_cmd/default_ini.h | 17 | 
20 files changed, 962 insertions, 5 deletions
| diff --git a/CMakeLists.txt b/CMakeLists.txt index 118572c03..dc782e252 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -350,6 +350,13 @@ function(create_target_directory_groups target_name)      endforeach()  endfunction() +# Prevent boost from linking against libs when building +add_definitions(-DBOOST_ERROR_CODE_HEADER_ONLY +    -DBOOST_SYSTEM_NO_LIB +    -DBOOST_DATE_TIME_NO_LIB +    -DBOOST_REGEX_NO_LIB +) +  enable_testing()  add_subdirectory(externals)  add_subdirectory(src) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f18239edb..9d0af02fd 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -77,6 +77,15 @@ else()              add_compile_options("-static")          endif()      endif() + +    if(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR MINGW) +        # GNU ar: Create thin archive files. +        # Requires binutils-2.19 or later. +        set(CMAKE_C_ARCHIVE_CREATE   "<CMAKE_AR> qcTP <TARGET> <LINK_FLAGS> <OBJECTS>") +        set(CMAKE_C_ARCHIVE_APPEND   "<CMAKE_AR> qTP  <TARGET> <LINK_FLAGS> <OBJECTS>") +        set(CMAKE_CXX_ARCHIVE_CREATE "<CMAKE_AR> qcTP <TARGET> <LINK_FLAGS> <OBJECTS>") +        set(CMAKE_CXX_ARCHIVE_APPEND "<CMAKE_AR> qTP  <TARGET> <LINK_FLAGS> <OBJECTS>") +    endif()  endif()  add_subdirectory(common) diff --git a/src/common/thread.h b/src/common/thread.h index 0cfd98be6..2fc071685 100644 --- a/src/common/thread.h +++ b/src/common/thread.h @@ -28,6 +28,15 @@ public:          is_set = false;      } +    template <class Duration> +    bool WaitFor(const std::chrono::duration<Duration>& time) { +        std::unique_lock lk{mutex}; +        if (!condvar.wait_for(lk, time, [this] { return is_set; })) +            return false; +        is_set = false; +        return true; +    } +      template <class Clock, class Duration>      bool WaitUntil(const std::chrono::time_point<Clock, Duration>& time) {          std::unique_lock lk{mutex}; diff --git a/src/core/hle/service/time/time_zone_manager.cpp b/src/core/hle/service/time/time_zone_manager.cpp index 717e81818..07b553a43 100644 --- a/src/core/hle/service/time/time_zone_manager.cpp +++ b/src/core/hle/service/time/time_zone_manager.cpp @@ -1019,6 +1019,15 @@ ResultCode TimeZoneManager::ToPosixTime(const TimeZoneRule& rules,      return RESULT_SUCCESS;  } +ResultCode TimeZoneManager::ToPosixTimeWithMyRule(const CalendarTime& calendar_time, +                                                  s64& posix_time) const { +    if (is_initialized) { +        return ToPosixTime(time_zone_rule, calendar_time, posix_time); +    } +    posix_time = 0; +    return ERROR_UNINITIALIZED_CLOCK; +} +  ResultCode TimeZoneManager::GetDeviceLocationName(LocationName& value) const {      if (!is_initialized) {          return ERROR_UNINITIALIZED_CLOCK; diff --git a/src/core/hle/service/time/time_zone_manager.h b/src/core/hle/service/time/time_zone_manager.h index 7c6f975ae..aaab0a1e0 100644 --- a/src/core/hle/service/time/time_zone_manager.h +++ b/src/core/hle/service/time/time_zone_manager.h @@ -39,6 +39,7 @@ public:      ResultCode ParseTimeZoneRuleBinary(TimeZoneRule& rules, FileSys::VirtualFile& vfs_file) const;      ResultCode ToPosixTime(const TimeZoneRule& rules, const CalendarTime& calendar_time,                             s64& posix_time) const; +    ResultCode ToPosixTimeWithMyRule(const CalendarTime& calendar_time, s64& posix_time) const;  private:      bool is_initialized{}; diff --git a/src/core/hle/service/time/time_zone_service.cpp b/src/core/hle/service/time/time_zone_service.cpp index 1566e778e..db57ae069 100644 --- a/src/core/hle/service/time/time_zone_service.cpp +++ b/src/core/hle/service/time/time_zone_service.cpp @@ -22,7 +22,7 @@ ITimeZoneService ::ITimeZoneService(TimeZone::TimeZoneContentManager& time_zone_          {100, &ITimeZoneService::ToCalendarTime, "ToCalendarTime"},          {101, &ITimeZoneService::ToCalendarTimeWithMyRule, "ToCalendarTimeWithMyRule"},          {201, &ITimeZoneService::ToPosixTime, "ToPosixTime"}, -        {202, nullptr, "ToPosixTimeWithMyRule"}, +        {202, &ITimeZoneService::ToPosixTimeWithMyRule, "ToPosixTimeWithMyRule"},      };      RegisterHandlers(functions);  } @@ -145,4 +145,26 @@ void ITimeZoneService::ToPosixTime(Kernel::HLERequestContext& ctx) {      ctx.WriteBuffer(&posix_time, sizeof(s64));  } +void ITimeZoneService::ToPosixTimeWithMyRule(Kernel::HLERequestContext& ctx) { +    LOG_DEBUG(Service_Time, "called"); + +    IPC::RequestParser rp{ctx}; +    const auto calendar_time{rp.PopRaw<TimeZone::CalendarTime>()}; + +    s64 posix_time{}; +    if (const ResultCode result{ +            time_zone_content_manager.GetTimeZoneManager().ToPosixTimeWithMyRule(calendar_time, +                                                                                 posix_time)}; +        result != RESULT_SUCCESS) { +        IPC::ResponseBuilder rb{ctx, 2}; +        rb.Push(result); +        return; +    } + +    IPC::ResponseBuilder rb{ctx, 3}; +    rb.Push(RESULT_SUCCESS); +    rb.PushRaw<u32>(1); // Number of times we're returning +    ctx.WriteBuffer(&posix_time, sizeof(s64)); +} +  } // namespace Service::Time diff --git a/src/core/hle/service/time/time_zone_service.h b/src/core/hle/service/time/time_zone_service.h index a92b4312b..cb495748b 100644 --- a/src/core/hle/service/time/time_zone_service.h +++ b/src/core/hle/service/time/time_zone_service.h @@ -22,6 +22,7 @@ private:      void ToCalendarTime(Kernel::HLERequestContext& ctx);      void ToCalendarTimeWithMyRule(Kernel::HLERequestContext& ctx);      void ToPosixTime(Kernel::HLERequestContext& ctx); +    void ToPosixTimeWithMyRule(Kernel::HLERequestContext& ctx);  private:      TimeZone::TimeZoneContentManager& time_zone_content_manager; diff --git a/src/core/settings.h b/src/core/settings.h index 9c98a9287..421e76f5f 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -401,6 +401,9 @@ struct Values {      std::string motion_device;      TouchscreenInput touchscreen;      std::atomic_bool is_device_reload_pending{true}; +    std::string udp_input_address; +    u16 udp_input_port; +    u8 udp_pad_index;      // Core      bool use_multi_core; diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt index 5b4e032bd..2520ba321 100644 --- a/src/input_common/CMakeLists.txt +++ b/src/input_common/CMakeLists.txt @@ -9,6 +9,12 @@ add_library(input_common STATIC      motion_emu.h      sdl/sdl.cpp      sdl/sdl.h +    udp/client.cpp +    udp/client.h +    udp/protocol.cpp +    udp/protocol.h +    udp/udp.cpp +    udp/udp.h  )  if(SDL2_FOUND) @@ -21,4 +27,4 @@ if(SDL2_FOUND)  endif()  create_target_directory_groups(input_common) -target_link_libraries(input_common PUBLIC core PRIVATE common) +target_link_libraries(input_common PUBLIC core PRIVATE common ${Boost_LIBRARIES}) diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index 8e66c1b15..9e028da89 100644 --- a/src/input_common/main.cpp +++ b/src/input_common/main.cpp @@ -9,6 +9,7 @@  #include "input_common/keyboard.h"  #include "input_common/main.h"  #include "input_common/motion_emu.h" +#include "input_common/udp/udp.h"  #ifdef HAVE_SDL2  #include "input_common/sdl/sdl.h"  #endif @@ -18,6 +19,7 @@ namespace InputCommon {  static std::shared_ptr<Keyboard> keyboard;  static std::shared_ptr<MotionEmu> motion_emu;  static std::unique_ptr<SDL::State> sdl; +static std::unique_ptr<CemuhookUDP::State> udp;  void Init() {      keyboard = std::make_shared<Keyboard>(); @@ -28,6 +30,8 @@ void Init() {      Input::RegisterFactory<Input::MotionDevice>("motion_emu", motion_emu);      sdl = SDL::Init(); + +    udp = CemuhookUDP::Init();  }  void Shutdown() { @@ -72,11 +76,13 @@ std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left,  namespace Polling {  std::vector<std::unique_ptr<DevicePoller>> GetPollers(DeviceType type) { +    std::vector<std::unique_ptr<DevicePoller>> pollers; +  #ifdef HAVE_SDL2 -    return sdl->GetPollers(type); -#else -    return {}; +    pollers = sdl->GetPollers(type);  #endif + +    return pollers;  }  } // namespace Polling diff --git a/src/input_common/udp/client.cpp b/src/input_common/udp/client.cpp new file mode 100644 index 000000000..5f5a9989c --- /dev/null +++ b/src/input_common/udp/client.cpp @@ -0,0 +1,287 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <array> +#include <chrono> +#include <cstring> +#include <functional> +#include <thread> +#include <boost/asio.hpp> +#include <boost/bind.hpp> +#include "common/logging/log.h" +#include "input_common/udp/client.h" +#include "input_common/udp/protocol.h" + +using boost::asio::ip::address_v4; +using boost::asio::ip::udp; + +namespace InputCommon::CemuhookUDP { + +struct SocketCallback { +    std::function<void(Response::Version)> version; +    std::function<void(Response::PortInfo)> port_info; +    std::function<void(Response::PadData)> pad_data; +}; + +class Socket { +public: +    using clock = std::chrono::system_clock; + +    explicit Socket(const std::string& host, u16 port, u8 pad_index, u32 client_id, +                    SocketCallback callback) +        : client_id(client_id), timer(io_service), +          send_endpoint(udp::endpoint(address_v4::from_string(host), port)), +          socket(io_service, udp::endpoint(udp::v4(), 0)), pad_index(pad_index), +          callback(std::move(callback)) {} + +    void Stop() { +        io_service.stop(); +    } + +    void Loop() { +        io_service.run(); +    } + +    void StartSend(const clock::time_point& from) { +        timer.expires_at(from + std::chrono::seconds(3)); +        timer.async_wait([this](const boost::system::error_code& error) { HandleSend(error); }); +    } + +    void StartReceive() { +        socket.async_receive_from( +            boost::asio::buffer(receive_buffer), receive_endpoint, +            [this](const boost::system::error_code& error, std::size_t bytes_transferred) { +                HandleReceive(error, bytes_transferred); +            }); +    } + +private: +    void HandleReceive(const boost::system::error_code& error, std::size_t bytes_transferred) { +        if (auto type = Response::Validate(receive_buffer.data(), bytes_transferred)) { +            switch (*type) { +            case Type::Version: { +                Response::Version version; +                std::memcpy(&version, &receive_buffer[sizeof(Header)], sizeof(Response::Version)); +                callback.version(std::move(version)); +                break; +            } +            case Type::PortInfo: { +                Response::PortInfo port_info; +                std::memcpy(&port_info, &receive_buffer[sizeof(Header)], +                            sizeof(Response::PortInfo)); +                callback.port_info(std::move(port_info)); +                break; +            } +            case Type::PadData: { +                Response::PadData pad_data; +                std::memcpy(&pad_data, &receive_buffer[sizeof(Header)], sizeof(Response::PadData)); +                callback.pad_data(std::move(pad_data)); +                break; +            } +            } +        } +        StartReceive(); +    } + +    void HandleSend(const boost::system::error_code& error) { +        // Send a request for getting port info for the pad +        Request::PortInfo port_info{1, {pad_index, 0, 0, 0}}; +        const auto port_message = Request::Create(port_info, client_id); +        std::memcpy(&send_buffer1, &port_message, PORT_INFO_SIZE); +        socket.send_to(boost::asio::buffer(send_buffer1), send_endpoint); + +        // Send a request for getting pad data for the pad +        Request::PadData pad_data{Request::PadData::Flags::Id, pad_index, EMPTY_MAC_ADDRESS}; +        const auto pad_message = Request::Create(pad_data, client_id); +        std::memcpy(send_buffer2.data(), &pad_message, PAD_DATA_SIZE); +        socket.send_to(boost::asio::buffer(send_buffer2), send_endpoint); +        StartSend(timer.expiry()); +    } + +    SocketCallback callback; +    boost::asio::io_service io_service; +    boost::asio::basic_waitable_timer<clock> timer; +    udp::socket socket; + +    u32 client_id{}; +    u8 pad_index{}; + +    static constexpr std::size_t PORT_INFO_SIZE = sizeof(Message<Request::PortInfo>); +    static constexpr std::size_t PAD_DATA_SIZE = sizeof(Message<Request::PadData>); +    std::array<u8, PORT_INFO_SIZE> send_buffer1; +    std::array<u8, PAD_DATA_SIZE> send_buffer2; +    udp::endpoint send_endpoint; + +    std::array<u8, MAX_PACKET_SIZE> receive_buffer; +    udp::endpoint receive_endpoint; +}; + +static void SocketLoop(Socket* socket) { +    socket->StartReceive(); +    socket->StartSend(Socket::clock::now()); +    socket->Loop(); +} + +Client::Client(std::shared_ptr<DeviceStatus> status, const std::string& host, u16 port, +               u8 pad_index, u32 client_id) +    : status(status) { +    StartCommunication(host, port, pad_index, client_id); +} + +Client::~Client() { +    socket->Stop(); +    thread.join(); +} + +void Client::ReloadSocket(const std::string& host, u16 port, u8 pad_index, u32 client_id) { +    socket->Stop(); +    thread.join(); +    StartCommunication(host, port, pad_index, client_id); +} + +void Client::OnVersion(Response::Version data) { +    LOG_TRACE(Input, "Version packet received: {}", data.version); +} + +void Client::OnPortInfo(Response::PortInfo data) { +    LOG_TRACE(Input, "PortInfo packet received: {}", data.model); +} + +void Client::OnPadData(Response::PadData data) { +    LOG_TRACE(Input, "PadData packet received"); +    if (data.packet_counter <= packet_sequence) { +        LOG_WARNING( +            Input, +            "PadData packet dropped because its stale info. Current count: {} Packet count: {}", +            packet_sequence, data.packet_counter); +        return; +    } +    packet_sequence = data.packet_counter; +    // TODO: Check how the Switch handles motions and how the CemuhookUDP motion +    // directions correspond to the ones of the Switch +    Common::Vec3f accel = Common::MakeVec<float>(data.accel.x, data.accel.y, data.accel.z); +    Common::Vec3f gyro = Common::MakeVec<float>(data.gyro.pitch, data.gyro.yaw, data.gyro.roll); +    { +        std::lock_guard guard(status->update_mutex); + +        status->motion_status = {accel, gyro}; + +        // TODO: add a setting for "click" touch. Click touch refers to a device that differentiates +        // between a simple "tap" and a hard press that causes the touch screen to click. +        const bool is_active = data.touch_1.is_active != 0; + +        float x = 0; +        float y = 0; + +        if (is_active && status->touch_calibration) { +            const u16 min_x = status->touch_calibration->min_x; +            const u16 max_x = status->touch_calibration->max_x; +            const u16 min_y = status->touch_calibration->min_y; +            const u16 max_y = status->touch_calibration->max_y; + +            x = (std::clamp(static_cast<u16>(data.touch_1.x), min_x, max_x) - min_x) / +                static_cast<float>(max_x - min_x); +            y = (std::clamp(static_cast<u16>(data.touch_1.y), min_y, max_y) - min_y) / +                static_cast<float>(max_y - min_y); +        } + +        status->touch_status = {x, y, is_active}; +    } +} + +void Client::StartCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id) { +    SocketCallback callback{[this](Response::Version version) { OnVersion(version); }, +                            [this](Response::PortInfo info) { OnPortInfo(info); }, +                            [this](Response::PadData data) { OnPadData(data); }}; +    LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port); +    socket = std::make_unique<Socket>(host, port, pad_index, client_id, callback); +    thread = std::thread{SocketLoop, this->socket.get()}; +} + +void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id, +                       std::function<void()> success_callback, +                       std::function<void()> failure_callback) { +    std::thread([=] { +        Common::Event success_event; +        SocketCallback callback{[](Response::Version version) {}, [](Response::PortInfo info) {}, +                                [&](Response::PadData data) { success_event.Set(); }}; +        Socket socket{host, port, pad_index, client_id, callback}; +        std::thread worker_thread{SocketLoop, &socket}; +        bool result = success_event.WaitFor(std::chrono::seconds(8)); +        socket.Stop(); +        worker_thread.join(); +        if (result) { +            success_callback(); +        } else { +            failure_callback(); +        } +    }) +        .detach(); +} + +CalibrationConfigurationJob::CalibrationConfigurationJob( +    const std::string& host, u16 port, u8 pad_index, u32 client_id, +    std::function<void(Status)> status_callback, +    std::function<void(u16, u16, u16, u16)> data_callback) { + +    std::thread([=] { +        constexpr u16 CALIBRATION_THRESHOLD = 100; + +        u16 min_x{UINT16_MAX}; +        u16 min_y{UINT16_MAX}; +        u16 max_x{}; +        u16 max_y{}; + +        Status current_status{Status::Initialized}; +        SocketCallback callback{[](Response::Version version) {}, [](Response::PortInfo info) {}, +                                [&](Response::PadData data) { +                                    if (current_status == Status::Initialized) { +                                        // Receiving data means the communication is ready now +                                        current_status = Status::Ready; +                                        status_callback(current_status); +                                    } +                                    if (!data.touch_1.is_active) { +                                        return; +                                    } +                                    LOG_DEBUG(Input, "Current touch: {} {}", data.touch_1.x, +                                              data.touch_1.y); +                                    min_x = std::min(min_x, static_cast<u16>(data.touch_1.x)); +                                    min_y = std::min(min_y, static_cast<u16>(data.touch_1.y)); +                                    if (current_status == Status::Ready) { +                                        // First touch - min data (min_x/min_y) +                                        current_status = Status::Stage1Completed; +                                        status_callback(current_status); +                                    } +                                    if (data.touch_1.x - min_x > CALIBRATION_THRESHOLD && +                                        data.touch_1.y - min_y > CALIBRATION_THRESHOLD) { +                                        // Set the current position as max value and finishes +                                        // configuration +                                        max_x = data.touch_1.x; +                                        max_y = data.touch_1.y; +                                        current_status = Status::Completed; +                                        data_callback(min_x, min_y, max_x, max_y); +                                        status_callback(current_status); + +                                        complete_event.Set(); +                                    } +                                }}; +        Socket socket{host, port, pad_index, client_id, callback}; +        std::thread worker_thread{SocketLoop, &socket}; +        complete_event.Wait(); +        socket.Stop(); +        worker_thread.join(); +    }) +        .detach(); +} + +CalibrationConfigurationJob::~CalibrationConfigurationJob() { +    Stop(); +} + +void CalibrationConfigurationJob::Stop() { +    complete_event.Set(); +} + +} // namespace InputCommon::CemuhookUDP diff --git a/src/input_common/udp/client.h b/src/input_common/udp/client.h new file mode 100644 index 000000000..0b21f4da6 --- /dev/null +++ b/src/input_common/udp/client.h @@ -0,0 +1,96 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <functional> +#include <memory> +#include <mutex> +#include <optional> +#include <string> +#include <thread> +#include <tuple> +#include <vector> +#include "common/common_types.h" +#include "common/thread.h" +#include "common/vector_math.h" + +namespace InputCommon::CemuhookUDP { + +constexpr u16 DEFAULT_PORT = 26760; +constexpr char DEFAULT_ADDR[] = "127.0.0.1"; + +class Socket; + +namespace Response { +struct PadData; +struct PortInfo; +struct Version; +} // namespace Response + +struct DeviceStatus { +    std::mutex update_mutex; +    std::tuple<Common::Vec3<float>, Common::Vec3<float>> motion_status; +    std::tuple<float, float, bool> touch_status; + +    // calibration data for scaling the device's touch area to 3ds +    struct CalibrationData { +        u16 min_x{}; +        u16 min_y{}; +        u16 max_x{}; +        u16 max_y{}; +    }; +    std::optional<CalibrationData> touch_calibration; +}; + +class Client { +public: +    explicit Client(std::shared_ptr<DeviceStatus> status, const std::string& host = DEFAULT_ADDR, +                    u16 port = DEFAULT_PORT, u8 pad_index = 0, u32 client_id = 24872); +    ~Client(); +    void ReloadSocket(const std::string& host = "127.0.0.1", u16 port = 26760, u8 pad_index = 0, +                      u32 client_id = 24872); + +private: +    void OnVersion(Response::Version); +    void OnPortInfo(Response::PortInfo); +    void OnPadData(Response::PadData); +    void StartCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id); + +    std::unique_ptr<Socket> socket; +    std::shared_ptr<DeviceStatus> status; +    std::thread thread; +    u64 packet_sequence = 0; +}; + +/// An async job allowing configuration of the touchpad calibration. +class CalibrationConfigurationJob { +public: +    enum class Status { +        Initialized, +        Ready, +        Stage1Completed, +        Completed, +    }; +    /** +     * Constructs and starts the job with the specified parameter. +     * +     * @param status_callback Callback for job status updates +     * @param data_callback Called when calibration data is ready +     */ +    explicit CalibrationConfigurationJob(const std::string& host, u16 port, u8 pad_index, +                                         u32 client_id, std::function<void(Status)> status_callback, +                                         std::function<void(u16, u16, u16, u16)> data_callback); +    ~CalibrationConfigurationJob(); +    void Stop(); + +private: +    Common::Event complete_event; +}; + +void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id, +                       std::function<void()> success_callback, +                       std::function<void()> failure_callback); + +} // namespace InputCommon::CemuhookUDP diff --git a/src/input_common/udp/protocol.cpp b/src/input_common/udp/protocol.cpp new file mode 100644 index 000000000..a982ac49d --- /dev/null +++ b/src/input_common/udp/protocol.cpp @@ -0,0 +1,79 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cstddef> +#include <cstring> +#include "common/logging/log.h" +#include "input_common/udp/protocol.h" + +namespace InputCommon::CemuhookUDP { + +static constexpr std::size_t GetSizeOfResponseType(Type t) { +    switch (t) { +    case Type::Version: +        return sizeof(Response::Version); +    case Type::PortInfo: +        return sizeof(Response::PortInfo); +    case Type::PadData: +        return sizeof(Response::PadData); +    } +    return 0; +} + +namespace Response { + +/** + * Returns Type if the packet is valid, else none + * + * Note: Modifies the buffer to zero out the crc (since thats the easiest way to check without + * copying the buffer) + */ +std::optional<Type> Validate(u8* data, std::size_t size) { +    if (size < sizeof(Header)) { +        LOG_DEBUG(Input, "Invalid UDP packet received"); +        return std::nullopt; +    } +    Header header{}; +    std::memcpy(&header, data, sizeof(Header)); +    if (header.magic != SERVER_MAGIC) { +        LOG_ERROR(Input, "UDP Packet has an unexpected magic value"); +        return std::nullopt; +    } +    if (header.protocol_version != PROTOCOL_VERSION) { +        LOG_ERROR(Input, "UDP Packet protocol mismatch"); +        return std::nullopt; +    } +    if (header.type < Type::Version || header.type > Type::PadData) { +        LOG_ERROR(Input, "UDP Packet is an unknown type"); +        return std::nullopt; +    } + +    // Packet size must equal sizeof(Header) + sizeof(Data) +    // and also verify that the packet info mentions the correct size. Since the spec includes the +    // type of the packet as part of the data, we need to include it in size calculations here +    // ie: payload_length == sizeof(T) + sizeof(Type) +    const std::size_t data_len = GetSizeOfResponseType(header.type); +    if (header.payload_length != data_len + sizeof(Type) || size < data_len + sizeof(Header)) { +        LOG_ERROR( +            Input, +            "UDP Packet payload length doesn't match. Received: {} PayloadLength: {} Expected: {}", +            size, header.payload_length, data_len + sizeof(Type)); +        return std::nullopt; +    } + +    const u32 crc32 = header.crc; +    boost::crc_32_type result; +    // zero out the crc in the buffer and then run the crc against it +    std::memset(&data[offsetof(Header, crc)], 0, sizeof(u32_le)); + +    result.process_bytes(data, data_len + sizeof(Header)); +    if (crc32 != result.checksum()) { +        LOG_ERROR(Input, "UDP Packet CRC check failed. Offset: {}", offsetof(Header, crc)); +        return std::nullopt; +    } +    return header.type; +} +} // namespace Response + +} // namespace InputCommon::CemuhookUDP diff --git a/src/input_common/udp/protocol.h b/src/input_common/udp/protocol.h new file mode 100644 index 000000000..1b521860a --- /dev/null +++ b/src/input_common/udp/protocol.h @@ -0,0 +1,256 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <optional> +#include <type_traits> +#include <vector> +#include <boost/crc.hpp> +#include "common/bit_field.h" +#include "common/swap.h" + +namespace InputCommon::CemuhookUDP { + +constexpr std::size_t MAX_PACKET_SIZE = 100; +constexpr u16 PROTOCOL_VERSION = 1001; +constexpr u32 CLIENT_MAGIC = 0x43555344; // DSUC (but flipped for LE) +constexpr u32 SERVER_MAGIC = 0x53555344; // DSUS (but flipped for LE) + +enum class Type : u32 { +    Version = 0x00100000, +    PortInfo = 0x00100001, +    PadData = 0x00100002, +}; + +struct Header { +    u32_le magic{}; +    u16_le protocol_version{}; +    u16_le payload_length{}; +    u32_le crc{}; +    u32_le id{}; +    ///> In the protocol, the type of the packet is not part of the header, but its convenient to +    ///> include in the header so the callee doesn't have to duplicate the type twice when building +    ///> the data +    Type type{}; +}; +static_assert(sizeof(Header) == 20, "UDP Message Header struct has wrong size"); +static_assert(std::is_trivially_copyable_v<Header>, "UDP Message Header is not trivially copyable"); + +using MacAddress = std::array<u8, 6>; +constexpr MacAddress EMPTY_MAC_ADDRESS = {0, 0, 0, 0, 0, 0}; + +#pragma pack(push, 1) +template <typename T> +struct Message { +    Header header{}; +    T data; +}; +#pragma pack(pop) + +template <typename T> +constexpr Type GetMessageType(); + +namespace Request { + +struct Version {}; +/** + * Requests the server to send information about what controllers are plugged into the ports + * In citra's case, we only have one controller, so for simplicity's sake, we can just send a + * request explicitly for the first controller port and leave it at that. In the future it would be + * nice to make this configurable + */ +constexpr u32 MAX_PORTS = 4; +struct PortInfo { +    u32_le pad_count{}; ///> Number of ports to request data for +    std::array<u8, MAX_PORTS> port; +}; +static_assert(std::is_trivially_copyable_v<PortInfo>, +              "UDP Request PortInfo is not trivially copyable"); + +/** + * Request the latest pad information from the server. If the server hasn't received this message + * from the client in a reasonable time frame, the server will stop sending updates. The default + * timeout seems to be 5 seconds. + */ +struct PadData { +    enum class Flags : u8 { +        AllPorts, +        Id, +        Mac, +    }; +    /// Determines which method will be used as a look up for the controller +    Flags flags{}; +    /// Index of the port of the controller to retrieve data about +    u8 port_id{}; +    /// Mac address of the controller to retrieve data about +    MacAddress mac; +}; +static_assert(sizeof(PadData) == 8, "UDP Request PadData struct has wrong size"); +static_assert(std::is_trivially_copyable_v<PadData>, +              "UDP Request PadData is not trivially copyable"); + +/** + * Creates a message with the proper header data that can be sent to the server. + * @param T data Request body to send + * @param client_id ID of the udp client (usually not checked on the server) + */ +template <typename T> +Message<T> Create(const T data, const u32 client_id = 0) { +    boost::crc_32_type crc; +    Header header{ +        CLIENT_MAGIC, PROTOCOL_VERSION, sizeof(T) + sizeof(Type), 0, client_id, GetMessageType<T>(), +    }; +    Message<T> message{header, data}; +    crc.process_bytes(&message, sizeof(Message<T>)); +    message.header.crc = crc.checksum(); +    return message; +} +} // namespace Request + +namespace Response { + +struct Version { +    u16_le version{}; +}; +static_assert(sizeof(Version) == 2, "UDP Response Version struct has wrong size"); +static_assert(std::is_trivially_copyable_v<Version>, +              "UDP Response Version is not trivially copyable"); + +struct PortInfo { +    u8 id{}; +    u8 state{}; +    u8 model{}; +    u8 connection_type{}; +    MacAddress mac; +    u8 battery{}; +    u8 is_pad_active{}; +}; +static_assert(sizeof(PortInfo) == 12, "UDP Response PortInfo struct has wrong size"); +static_assert(std::is_trivially_copyable_v<PortInfo>, +              "UDP Response PortInfo is not trivially copyable"); + +#pragma pack(push, 1) +struct PadData { +    PortInfo info{}; +    u32_le packet_counter{}; + +    u16_le digital_button{}; +    // The following union isn't trivially copyable but we don't use this input anyway. +    // union DigitalButton { +    //     u16_le button; +    //     BitField<0, 1, u16> button_1;   // Share +    //     BitField<1, 1, u16> button_2;   // L3 +    //     BitField<2, 1, u16> button_3;   // R3 +    //     BitField<3, 1, u16> button_4;   // Options +    //     BitField<4, 1, u16> button_5;   // Up +    //     BitField<5, 1, u16> button_6;   // Right +    //     BitField<6, 1, u16> button_7;   // Down +    //     BitField<7, 1, u16> button_8;   // Left +    //     BitField<8, 1, u16> button_9;   // L2 +    //     BitField<9, 1, u16> button_10;  // R2 +    //     BitField<10, 1, u16> button_11; // L1 +    //     BitField<11, 1, u16> button_12; // R1 +    //     BitField<12, 1, u16> button_13; // Triangle +    //     BitField<13, 1, u16> button_14; // Circle +    //     BitField<14, 1, u16> button_15; // Cross +    //     BitField<15, 1, u16> button_16; // Square +    // } digital_button; + +    u8 home; +    /// If the device supports a "click" on the touchpad, this will change to 1 when a click happens +    u8 touch_hard_press{}; +    u8 left_stick_x{}; +    u8 left_stick_y{}; +    u8 right_stick_x{}; +    u8 right_stick_y{}; + +    struct AnalogButton { +        u8 button_8{}; +        u8 button_7{}; +        u8 button_6{}; +        u8 button_5{}; +        u8 button_12{}; +        u8 button_11{}; +        u8 button_10{}; +        u8 button_9{}; +        u8 button_16{}; +        u8 button_15{}; +        u8 button_14{}; +        u8 button_13{}; +    } analog_button; + +    struct TouchPad { +        u8 is_active{}; +        u8 id{}; +        u16_le x{}; +        u16_le y{}; +    } touch_1, touch_2; + +    u64_le motion_timestamp; + +    struct Accelerometer { +        float x{}; +        float y{}; +        float z{}; +    } accel; + +    struct Gyroscope { +        float pitch{}; +        float yaw{}; +        float roll{}; +    } gyro; +}; +#pragma pack(pop) + +static_assert(sizeof(PadData) == 80, "UDP Response PadData struct has wrong size "); +static_assert(std::is_trivially_copyable_v<PadData>, +              "UDP Response PadData is not trivially copyable"); + +static_assert(sizeof(Message<PadData>) == MAX_PACKET_SIZE, +              "UDP MAX_PACKET_SIZE is no longer larger than Message<PadData>"); + +static_assert(sizeof(PadData::AnalogButton) == 12, +              "UDP Response AnalogButton struct has wrong size "); +static_assert(sizeof(PadData::TouchPad) == 6, "UDP Response TouchPad struct has wrong size "); +static_assert(sizeof(PadData::Accelerometer) == 12, +              "UDP Response Accelerometer struct has wrong size "); +static_assert(sizeof(PadData::Gyroscope) == 12, "UDP Response Gyroscope struct has wrong size "); + +/** + * Create a Response Message from the data + * @param data array of bytes sent from the server + * @return boost::none if it failed to parse or Type if it succeeded. The client can then safely + * copy the data into the appropriate struct for that Type + */ +std::optional<Type> Validate(u8* data, std::size_t size); + +} // namespace Response + +template <> +constexpr Type GetMessageType<Request::Version>() { +    return Type::Version; +} +template <> +constexpr Type GetMessageType<Request::PortInfo>() { +    return Type::PortInfo; +} +template <> +constexpr Type GetMessageType<Request::PadData>() { +    return Type::PadData; +} +template <> +constexpr Type GetMessageType<Response::Version>() { +    return Type::Version; +} +template <> +constexpr Type GetMessageType<Response::PortInfo>() { +    return Type::PortInfo; +} +template <> +constexpr Type GetMessageType<Response::PadData>() { +    return Type::PadData; +} +} // namespace InputCommon::CemuhookUDP diff --git a/src/input_common/udp/udp.cpp b/src/input_common/udp/udp.cpp new file mode 100644 index 000000000..a80f38614 --- /dev/null +++ b/src/input_common/udp/udp.cpp @@ -0,0 +1,96 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/logging/log.h" +#include "common/param_package.h" +#include "core/frontend/input.h" +#include "core/settings.h" +#include "input_common/udp/client.h" +#include "input_common/udp/udp.h" + +namespace InputCommon::CemuhookUDP { + +class UDPTouchDevice final : public Input::TouchDevice { +public: +    explicit UDPTouchDevice(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {} +    std::tuple<float, float, bool> GetStatus() const { +        std::lock_guard guard(status->update_mutex); +        return status->touch_status; +    } + +private: +    std::shared_ptr<DeviceStatus> status; +}; + +class UDPMotionDevice final : public Input::MotionDevice { +public: +    explicit UDPMotionDevice(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {} +    std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() const { +        std::lock_guard guard(status->update_mutex); +        return status->motion_status; +    } + +private: +    std::shared_ptr<DeviceStatus> status; +}; + +class UDPTouchFactory final : public Input::Factory<Input::TouchDevice> { +public: +    explicit UDPTouchFactory(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {} + +    std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override { +        { +            std::lock_guard guard(status->update_mutex); +            status->touch_calibration.emplace(); +            // These default values work well for DS4 but probably not other touch inputs +            status->touch_calibration->min_x = params.Get("min_x", 100); +            status->touch_calibration->min_y = params.Get("min_y", 50); +            status->touch_calibration->max_x = params.Get("max_x", 1800); +            status->touch_calibration->max_y = params.Get("max_y", 850); +        } +        return std::make_unique<UDPTouchDevice>(status); +    } + +private: +    std::shared_ptr<DeviceStatus> status; +}; + +class UDPMotionFactory final : public Input::Factory<Input::MotionDevice> { +public: +    explicit UDPMotionFactory(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {} + +    std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override { +        return std::make_unique<UDPMotionDevice>(status); +    } + +private: +    std::shared_ptr<DeviceStatus> status; +}; + +State::State() { +    auto status = std::make_shared<DeviceStatus>(); +    client = +        std::make_unique<Client>(status, Settings::values.udp_input_address, +                                 Settings::values.udp_input_port, Settings::values.udp_pad_index); + +    Input::RegisterFactory<Input::TouchDevice>("cemuhookudp", +                                               std::make_shared<UDPTouchFactory>(status)); +    Input::RegisterFactory<Input::MotionDevice>("cemuhookudp", +                                                std::make_shared<UDPMotionFactory>(status)); +} + +State::~State() { +    Input::UnregisterFactory<Input::TouchDevice>("cemuhookudp"); +    Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp"); +} + +void State::ReloadUDPClient() { +    client->ReloadSocket(Settings::values.udp_input_address, Settings::values.udp_input_port, +                         Settings::values.udp_pad_index); +} + +std::unique_ptr<State> Init() { +    return std::make_unique<State>(); +} +} // namespace InputCommon::CemuhookUDP diff --git a/src/input_common/udp/udp.h b/src/input_common/udp/udp.h new file mode 100644 index 000000000..ea3de60bb --- /dev/null +++ b/src/input_common/udp/udp.h @@ -0,0 +1,27 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <memory> +#include <unordered_map> +#include "input_common/main.h" +#include "input_common/udp/client.h" + +namespace InputCommon::CemuhookUDP { + +class UDPTouchDevice; +class UDPMotionDevice; + +class State { +public: +    State(); +    ~State(); +    void ReloadUDPClient(); + +private: +    std::unique_ptr<Client> client; +}; + +std::unique_ptr<State> Init(); + +} // namespace InputCommon::CemuhookUDP diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp index a4acb3796..3c5bdd377 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.cpp +++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp @@ -260,6 +260,10 @@ CachedProgram BuildShader(const Device& device, u64 unique_identifier, ShaderTyp                    "#extension GL_NV_shader_thread_group : require\n"                    "#extension GL_NV_shader_thread_shuffle : require\n";      } +    // This pragma stops Nvidia's driver from over optimizing math (probably using fp16 operations) +    // on places where we don't want to. +    // Thanks to Ryujinx for finding this workaround. +    source += "#pragma optionNV(fastmath off)\n";      if (shader_type == ShaderType::Geometry) {          const auto [glsl_topology, max_vertices] = GetPrimitiveDescription(variant.primitive_mode); diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index f92a4b3c3..59918847a 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -10,6 +10,7 @@  #include "core/hle/service/acc/profile_manager.h"  #include "core/hle/service/hid/controllers/npad.h"  #include "input_common/main.h" +#include "input_common/udp/client.h"  #include "yuzu/configuration/config.h"  #include "yuzu/uisettings.h" @@ -429,6 +430,16 @@ void Config::ReadControlValues() {                      QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01"))              .toString()              .toStdString(); +    Settings::values.udp_input_address = +        ReadSetting(QStringLiteral("udp_input_address"), +                    QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_ADDR)) +            .toString() +            .toStdString(); +    Settings::values.udp_input_port = static_cast<u16>( +        ReadSetting(QStringLiteral("udp_input_port"), InputCommon::CemuhookUDP::DEFAULT_PORT) +            .toInt()); +    Settings::values.udp_pad_index = +        static_cast<u8>(ReadSetting(QStringLiteral("udp_pad_index"), 0).toUInt());      qt_config->endGroup();  } @@ -911,6 +922,12 @@ void Config::SaveControlValues() {                   QString::fromStdString(Settings::values.motion_device),                   QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01"));      WriteSetting(QStringLiteral("keyboard_enabled"), Settings::values.keyboard_enabled, false); +    WriteSetting(QStringLiteral("udp_input_address"), +                 QString::fromStdString(Settings::values.udp_input_address), +                 QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_ADDR)); +    WriteSetting(QStringLiteral("udp_input_port"), Settings::values.udp_input_port, +                 InputCommon::CemuhookUDP::DEFAULT_PORT); +    WriteSetting(QStringLiteral("udp_pad_index"), Settings::values.udp_pad_index, 0);      qt_config->endGroup();  } diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index 1a812cb87..161583b54 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp @@ -12,6 +12,7 @@  #include "core/hle/service/acc/profile_manager.h"  #include "core/settings.h"  #include "input_common/main.h" +#include "input_common/udp/client.h"  #include "yuzu_cmd/config.h"  #include "yuzu_cmd/default_ini.h" @@ -297,6 +298,10 @@ void Config::ReadValues() {          sdl2_config->GetInteger("ControlsGeneral", "touch_diameter_x", 15);      Settings::values.touchscreen.diameter_y =          sdl2_config->GetInteger("ControlsGeneral", "touch_diameter_y", 15); +    Settings::values.udp_input_address = +        sdl2_config->Get("Controls", "udp_input_address", InputCommon::CemuhookUDP::DEFAULT_ADDR); +    Settings::values.udp_input_port = static_cast<u16>(sdl2_config->GetInteger( +        "Controls", "udp_input_port", InputCommon::CemuhookUDP::DEFAULT_PORT));      std::transform(keyboard_keys.begin(), keyboard_keys.end(),                     Settings::values.keyboard_keys.begin(), InputCommon::GenerateKeyboardParam); diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h index 8d18a4a5a..e829f8695 100644 --- a/src/yuzu_cmd/default_ini.h +++ b/src/yuzu_cmd/default_ini.h @@ -69,12 +69,29 @@ rstick=  #  - "motion_emu" (default) for emulating motion input from mouse input. Required parameters:  #      - "update_period": update period in milliseconds (default to 100)  #      - "sensitivity": the coefficient converting mouse movement to tilting angle (default to 0.01) +#  - "cemuhookudp" reads motion input from a udp server that uses cemuhook's udp protocol  motion_device=  # for touch input, the following devices are available:  #  - "emu_window" (default) for emulating touch input from mouse input to the emulation window. No parameters required +#  - "cemuhookudp" reads touch input from a udp server that uses cemuhook's udp protocol +#      - "min_x", "min_y", "max_x", "max_y": defines the udp device's touch screen coordinate system  touch_device= +# Most desktop operating systems do not expose a way to poll the motion state of the controllers +# so as a way around it, cemuhook created a udp client/server protocol to broadcast the data directly +# from a controller device to the client program. Citra has a client that can connect and read +# from any cemuhook compatible motion program. + +# IPv4 address of the udp input server (Default "127.0.0.1") +udp_input_address= + +# Port of the udp input server. (Default 26760) +udp_input_port= + +# The pad to request data on. Should be between 0 (Pad 1) and 3 (Pad 4). (Default 0) +udp_pad_index= +  [Core]  # Whether to use multi-core for CPU emulation  # 0 (default): Disabled, 1: Enabled | 
