diff --git a/core/sdl/dreamconn.cpp b/core/sdl/dreamconn.cpp index 54829e1b8..76d5ed06e 100644 --- a/core/sdl/dreamconn.cpp +++ b/core/sdl/dreamconn.cpp @@ -26,6 +26,11 @@ #include #include #include +#include +#include +#include +#include +#include #if defined(__linux__) || (defined(__APPLE__) && defined(TARGET_OS_MAC)) #include @@ -38,256 +43,568 @@ void createDreamConnDevices(std::shared_ptr dreamconn, bool gameStart); -static asio::error_code sendMsg(const MapleMsg& msg, asio::ip::tcp::iostream& stream, asio::io_context& io_context, asio::serial_port& serial_handler, int dreamcastControllerType) +class DreamcastControllerConnection { - std::ostringstream s; - s.fill('0'); - if (dreamcastControllerType == TYPE_DREAMCASTCONTROLLERUSB) +protected: + //! The maple bus index [0,3] + const int bus; + +public: + DreamcastControllerConnection(const DreamcastControllerConnection&) = delete; + DreamcastControllerConnection() = delete; + + explicit DreamcastControllerConnection(int bus) : bus(bus) + {} + + std::optional connect(){ + if (!establishConnection()) { + return std::nullopt; + } + + // Now get the controller configuration + MapleMsg msg; + msg.command = MDCF_GetCondition; + msg.destAP = (bus << 6) | 0x20; + msg.originAP = bus << 6; + msg.setData(MFID_0_Input); + + asio::error_code ec = sendMsg(msg); + if (ec) + { + WARN_LOG(INPUT, "DreamcastController[%d] connection failed: %s", bus, ec.message().c_str()); + disconnect(); + return std::nullopt; + } + if (!receiveMsg(msg)) { + WARN_LOG(INPUT, "DreamcastController[%d] read timeout", bus); + disconnect(); + return std::nullopt; + } + + onConnectComplete(); + + return msg; + } + + virtual void disconnect() = 0; + virtual asio::error_code sendMsg(const MapleMsg& msg) = 0; + virtual bool receiveMsg(MapleMsg& msg) = 0; + +protected: + virtual bool establishConnection() = 0; + virtual void onConnectComplete() = 0; + + std::string msgToString(const MapleMsg& msg, const std::string& delim = " ") { - // Messages to Dreamcast Controller USB need to be prefixed to trigger the correct parser - s << "X "; - } - - s << std::hex << std::uppercase - << std::setw(2) << (u32)msg.command << " " - << std::setw(2) << (u32)msg.destAP << " " - << std::setw(2) << (u32)msg.originAP << " " - << std::setw(2) << (u32)msg.size; - const u32 sz = msg.getDataSize(); - for (u32 i = 0; i < sz; i++) - s << " " << std::setw(2) << (u32)msg.data[i]; - s << "\r\n"; - - asio::error_code ec; - - if (dreamcastControllerType == TYPE_DREAMCONN) + std::ostringstream s; + s.fill('0'); + + s << std::hex << std::uppercase + << std::setw(2) << (u32)msg.command << " " + << std::setw(2) << (u32)msg.destAP << " " + << std::setw(2) << (u32)msg.originAP << " " + << std::setw(2) << (u32)msg.size; + const u32 sz = msg.getDataSize(); + for (u32 i = 0; i < sz; i++) + s << " " << std::setw(2) << (u32)msg.data[i]; + s << "\r\n"; + + return s.str(); + } +}; + +class DreamConnConnection : public DreamcastControllerConnection +{ + //! Base port of communication to DreamConn + static constexpr u16 BASE_PORT = 37393; + //! Stream to a DreamConn device + asio::ip::tcp::iostream iostream; + +public: + //! DreamConn VID:4457 PID:4443 + static constexpr const char* VID_PID_GUID = "5744000043440000"; + +public: + DreamConnConnection(const DreamConnConnection&) = delete; + DreamConnConnection() = delete; + + explicit DreamConnConnection(int bus) : DreamcastControllerConnection(bus) + {} + + ~DreamConnConnection() { + disconnect(); + } + + bool establishConnection() override { +#if !defined(_WIN32) + WARN_LOG(INPUT, "DreamcastController[%d] connection failed: DreamConn+ / DreamConn S Controller supported on Windows only", bus); + return false; +#else + iostream = asio::ip::tcp::iostream("localhost", std::to_string(BASE_PORT + bus)); + if (!iostream) { + WARN_LOG(INPUT, "DreamcastController[%d] connection failed: %s", bus, iostream.error().message().c_str()); + disconnect(); + return false; + } + iostream.expires_from_now(std::chrono::seconds(1)); + return true; +#endif + } + + void onConnectComplete() override { + iostream.expires_from_now(std::chrono::duration::max()); // don't use a 64-bit based duration to avoid overflow + } + + void disconnect() override { - if (!stream) - return asio::error::not_connected; - asio::ip::tcp::socket& sock = static_cast(stream.socket()); - asio::write(sock, asio::buffer(s.str()), ec); + if (iostream) { + iostream.close(); + } } - else if (dreamcastControllerType == TYPE_DREAMCASTCONTROLLERUSB) + + asio::error_code sendMsg(const MapleMsg& msg) override { - io_context.run(); - io_context.reset(); - - if (!serial_handler.is_open()) + const std::string msgStr = msgToString(msg); + asio::error_code ec; + + if (!iostream) { return asio::error::not_connected; - asio::async_write(serial_handler, asio::buffer(s.str()), asio::transfer_exactly(s.str().size()), [ &serial_handler](const asio::error_code& error, size_t bytes_transferred) - { - if (error) { - serial_handler.cancel(); - } - }); + } + asio::ip::tcp::socket& sock = static_cast(iostream.socket()); + asio::write(sock, asio::buffer(msgStr), ec); + + return ec; } - - return ec; -} -static bool receiveMsg(MapleMsg& msg, std::istream& stream, asio::serial_port& serial_handler, int dreamcastControllerType) -{ - std::string response; - - if (dreamcastControllerType == TYPE_DREAMCONN) + bool receiveMsg(MapleMsg& msg) override { - if (!std::getline(stream, response)) + std::string response; + + if (!std::getline(iostream, response)) return false; sscanf(response.c_str(), "%hhx %hhx %hhx %hhx", &msg.command, &msg.destAP, &msg.originAP, &msg.size); if ((msg.getDataSize() - 1) * 3 + 13 >= response.length()) return false; for (unsigned i = 0; i < msg.getDataSize(); i++) sscanf(&response[i * 3 + 12], "%hhx", &msg.data[i]); - return !stream.fail(); + return !iostream.fail(); + + return false; } - else if (dreamcastControllerType == TYPE_DREAMCASTCONTROLLERUSB) - { +}; + +//! See: https://github.com/OrangeFox86/DreamcastControllerUsbPico +class DreamcastControllerUsbPicoConnection : public DreamcastControllerConnection +{ + //! Asynchronous context for serial_handler + asio::io_context io_context; + //! Output buffer data for serial_handler + std::string serial_out_data; + //! Handles communication to DreamcastControllerUsbPico + asio::serial_port serial_handler{io_context}; + //! Set to true while an async write is in progress with serial_handler + bool serial_write_in_progress = false; + //! Signaled when serial_write_in_progress transitions to false + std::condition_variable write_cv; + //! Mutex for write_cv and serializes access to serial_write_in_progress + std::mutex write_cv_mutex; + //! Input stream buffer from serial_handler + asio::streambuf serial_read_buffer; + //! Thread which runs the io_context + std::unique_ptr io_context_thread; + //! Contains queue of incoming lines from serial + std::list read_queue; + //! Signaled when data is in read_queue + std::condition_variable read_cv; + //! Mutex for read_cv and serializes access to read_queue + std::mutex read_cv_mutex; + //! Current timeout in milliseconds + std::chrono::milliseconds timeout_ms; + +public: + //! Dreamcast Controller USB VID:1209 PID:2f07 + static constexpr const char* VID_PID_GUID = "09120000072f0000"; + +public: + DreamcastControllerUsbPicoConnection(const DreamcastControllerUsbPicoConnection&) = delete; + DreamcastControllerUsbPicoConnection() = delete; + + explicit DreamcastControllerUsbPicoConnection(int bus) : DreamcastControllerConnection(bus) + {} + + ~DreamcastControllerUsbPicoConnection(){ + disconnect(); + } + + bool establishConnection() override { asio::error_code ec; - - char c; - for (int i = 0; i < 2; ++i) + + // Timeout is 1 second while establishing connection + timeout_ms = std::chrono::seconds(1); + + // the serial port isn't ready at this point, so we need to sleep briefly + // we probably should have a better way to handle this + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + + serial_handler = asio::serial_port(io_context); + io_context.reset(); + + std::string serial_device = ""; + + // use user-configured serial device if available, fallback to first available + if (cfgLoadStr("input", "DreamcastControllerUsbSerialDevice", "default") != "default") { + serial_device = cfgLoadStr("input", "DreamcastControllerUsbSerialDevice", "default"); + NOTICE_LOG(INPUT, "DreamcastController[%d] connecting to user-configured serial device: %s", bus, serial_device.c_str()); + } + else { - // discard the first message as we are interested in the second only which returns the controller configuration - response = ""; - while (serial_handler.read_some(asio::buffer(&c, 1), ec) > 0) + serial_device = getFirstSerialDevice(); + NOTICE_LOG(INPUT, "DreamcastController[%d] connecting to autoselected serial device: %s", bus, serial_device.c_str()); + } + + serial_handler.open(serial_device, ec); + + if (ec || !serial_handler.is_open()) { + WARN_LOG(INPUT, "DreamcastController[%d] connection failed: %s", bus, ec.message().c_str()); + disconnect(); + return false; + } + + // This must be done before the io_context is run because it will keep io_context from returning immediately + startSerialRead(); + + io_context_thread = std::make_unique([this](){contextThreadEnty();}); + + return true; + } + + void onConnectComplete() override { + // Timeout is extended to 5 seconds for all other communication after connection + timeout_ms = std::chrono::seconds(5); + } + + void disconnect() override + { + io_context.stop(); + + if (serial_handler.is_open()) { + try { - if (!serial_handler.is_open()) - return false; - if (c == '\n') - break; - response += c; + serial_handler.cancel(); + } + catch(const asio::system_error&) + { + // Ignore cancel errors + } + } + + try + { + serial_handler.close(); + } + catch(const asio::system_error&) + { + // Ignore closing errors + } + } + + void contextThreadEnty() + { + // This context should never exit until disconnect due to read handler automatically rearming + io_context.run(); + } + + asio::error_code sendMsg(const MapleMsg& msg) override + { + asio::error_code ec; + + if (!serial_handler.is_open()) { + return asio::error::not_connected; + } + + // Wait for last write to complete + std::unique_lock lock(write_cv_mutex); + const std::chrono::steady_clock::time_point expiration = std::chrono::steady_clock::now() + timeout_ms; + if (!write_cv.wait_until(lock, expiration, [this](){return (!serial_write_in_progress || !serial_handler.is_open());})) + { + return asio::error::timed_out; + } + + // Check again before continuing + if (!serial_handler.is_open()) { + return asio::error::not_connected; + } + + // Clear out the read buffer before writing next command + read_queue.clear(); + + serial_write_in_progress = true; + // Messages to Dreamcast Controller USB need to be prefixed to trigger the correct parser + serial_out_data = std::string("X ") + msgToString(msg); + asio::async_write(serial_handler, asio::buffer(serial_out_data), asio::transfer_exactly(serial_out_data.size()), [this](const asio::error_code& error, size_t bytes_transferred) + { + std::unique_lock lock(write_cv_mutex); + if (error) { + try + { + serial_handler.cancel(); + } + catch(const asio::system_error&) + { + // Ignore cancel errors + } } - response.pop_back(); + serial_write_in_progress = false; + write_cv.notify_all(); + }); + + return ec; + } + + bool receiveMsg(MapleMsg& msg) override + { + std::string response; + + // Wait for at least 2 lines to be received (first line is echo back) + std::unique_lock lock(read_cv_mutex); + const std::chrono::steady_clock::time_point expiration = std::chrono::steady_clock::now() + timeout_ms; + if (!read_cv.wait_until(lock, expiration, [this](){return ((read_queue.size() >= 2) || !serial_handler.is_open());})) + { + // Timeout + return false; + } + + if (read_queue.size() < 2) { + // Connection was closed before data could be received + return false; } - + + // discard the first message as we are interested in the second only which returns the controller configuration + response = std::move(read_queue.back()); + read_queue.clear(); + sscanf(response.c_str(), "%hhx %hhx %hhx %hhx", &msg.command, &msg.destAP, &msg.originAP, &msg.size); - - if (!ec && serial_handler.is_open()) + + if (serial_handler.is_open()) { return true; - else + } + else { return false; - } - - return false; -} + } -static std::string getFirstSerialDevice() { - - // On Windows, we get the first serial device matching our VID/PID -#if defined(_WIN32) - HDEVINFO deviceInfoSet = SetupDiGetClassDevs(NULL, "USB", NULL, DIGCF_PRESENT | DIGCF_ALLCLASSES); - if (deviceInfoSet == INVALID_HANDLE_VALUE) { - return ""; + return false; } - SP_DEVINFO_DATA deviceInfoData; - deviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA); +private: + static std::string getFirstSerialDevice() { - for (DWORD i = 0; SetupDiEnumDeviceInfo(deviceInfoSet, i, &deviceInfoData); ++i) { - DWORD dataType, bufferSize = 0; - SetupDiGetDeviceRegistryProperty(deviceInfoSet, &deviceInfoData, SPDRP_HARDWAREID, &dataType, NULL, 0, &bufferSize); + // On Windows, we get the first serial device matching our VID/PID +#if defined(_WIN32) + HDEVINFO deviceInfoSet = SetupDiGetClassDevs(NULL, "USB", NULL, DIGCF_PRESENT | DIGCF_ALLCLASSES); + if (deviceInfoSet == INVALID_HANDLE_VALUE) { + return ""; + } - if (bufferSize > 0) { - std::vector buffer(bufferSize); - if (SetupDiGetDeviceRegistryProperty(deviceInfoSet, &deviceInfoData, SPDRP_HARDWAREID, &dataType, (PBYTE)buffer.data(), bufferSize, NULL)) { - std::string hardwareId(buffer.begin(), buffer.end()); - if (hardwareId.find("VID_1209") != std::string::npos && hardwareId.find("PID_2F07") != std::string::npos) { - HKEY deviceKey = SetupDiOpenDevRegKey(deviceInfoSet, &deviceInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ); - if (deviceKey != INVALID_HANDLE_VALUE) { - char portName[256]; - DWORD portNameSize = sizeof(portName); - if (RegQueryValueEx(deviceKey, "PortName", NULL, NULL, (LPBYTE)portName, &portNameSize) == ERROR_SUCCESS) { + SP_DEVINFO_DATA deviceInfoData; + deviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA); + + for (DWORD i = 0; SetupDiEnumDeviceInfo(deviceInfoSet, i, &deviceInfoData); ++i) { + DWORD dataType, bufferSize = 0; + SetupDiGetDeviceRegistryProperty(deviceInfoSet, &deviceInfoData, SPDRP_HARDWAREID, &dataType, NULL, 0, &bufferSize); + + if (bufferSize > 0) { + std::vector buffer(bufferSize); + if (SetupDiGetDeviceRegistryProperty(deviceInfoSet, &deviceInfoData, SPDRP_HARDWAREID, &dataType, (PBYTE)buffer.data(), bufferSize, NULL)) { + std::string hardwareId(buffer.begin(), buffer.end()); + if (hardwareId.find("VID_1209") != std::string::npos && hardwareId.find("PID_2F07") != std::string::npos) { + HKEY deviceKey = SetupDiOpenDevRegKey(deviceInfoSet, &deviceInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ); + if (deviceKey != INVALID_HANDLE_VALUE) { + char portName[256]; + DWORD portNameSize = sizeof(portName); + if (RegQueryValueEx(deviceKey, "PortName", NULL, NULL, (LPBYTE)portName, &portNameSize) == ERROR_SUCCESS) { + RegCloseKey(deviceKey); + SetupDiDestroyDeviceInfoList(deviceInfoSet); + return std::string(portName); + } RegCloseKey(deviceKey); - SetupDiDestroyDeviceInfoList(deviceInfoSet); - return std::string(portName); } - RegCloseKey(deviceKey); } } } } - } - - SetupDiDestroyDeviceInfoList(deviceInfoSet); - return ""; + + SetupDiDestroyDeviceInfoList(deviceInfoSet); + return ""; #endif - + +#if defined(__linux__) || (defined(__APPLE__) && defined(TARGET_OS_MAC)) // On MacOS/Linux, we get the first serial device matching the device prefix std::string device_prefix = ""; -#if defined(__linux__) || (defined(__APPLE__) && defined(TARGET_OS_MAC)) - + #if defined(__linux__) - device_prefix = "ttyACM"; + device_prefix = "ttyACM"; #elif (defined(__APPLE__) && defined(TARGET_OS_MAC)) - device_prefix = "tty.usbmodem"; + device_prefix = "tty.usbmodem"; #endif - - std::string path = "/dev/"; - DIR *dir; - struct dirent *ent; - if ((dir = opendir(path.c_str())) != NULL) { - while ((ent = readdir(dir)) != NULL) { - std::string device = ent->d_name; - if (device.find(device_prefix) != std::string::npos) { - closedir(dir); - return path + device; + + std::string path = "/dev/"; + DIR *dir; + struct dirent *ent; + if ((dir = opendir(path.c_str())) != NULL) { + while ((ent = readdir(dir)) != NULL) { + std::string device = ent->d_name; + if (device.find(device_prefix) != std::string::npos) { + closedir(dir); + return path + device; + } } + closedir(dir); } - closedir(dir); - } - return ""; + return ""; #endif -} + } -void DreamConn::connect() -{ - maple_io_connected = false; - - asio::error_code ec; - - switch (dreamcastControllerType) { - case TYPE_DREAMCONN: - { -#if !defined(_WIN32) - WARN_LOG(INPUT, "DreamcastController[%d] connection failed: DreamConn+ / DreamConn S Controller supported on Windows only", bus); - return; -#endif - iostream = asio::ip::tcp::iostream("localhost", std::to_string(BASE_PORT + bus)); - if (!iostream) { - WARN_LOG(INPUT, "DreamcastController[%d] connection failed: %s", bus, iostream.error().message().c_str()); - disconnect(); - return; + void startSerialRead() + { + serialReadHandler(asio::error_code(), 0); + // Just to make sure initial data is cleared off of incoming buffer + io_context.poll_one(); + read_queue.clear(); + } + + void serialReadHandler(const asio::error_code& error, std::size_t size) + { + if (error) { + std::lock_guard lock(read_cv_mutex); + try + { + serial_handler.cancel(); } - iostream.expires_from_now(std::chrono::seconds(1)); - break; + catch(const asio::system_error&) + { + // Ignore cancel errors + } + read_cv.notify_all(); } - case TYPE_DREAMCASTCONTROLLERUSB: + else { + // Rearm the read + asio::async_read_until( + serial_handler, + serial_read_buffer, + '\n', + [this](const asio::error_code& error, std::size_t size) -> void { + if (size > 0) + { + // Lock access to read_queue + std::lock_guard lock(read_cv_mutex); + // Consume the received data + if (consumeReadBuffer() > 0) + { + // New lines available + read_cv.notify_all(); + } + } + // Auto reload read - io_context will always have work to do + serialReadHandler(error, size); + } + ); + } + } + + int consumeReadBuffer() { + if (serial_read_buffer.size() <= 0) { + return 0; + } + + int numberOfLines = 0; + while (true) { - // the serial port isn't ready at this point, so we need to sleep briefly - // we probably should have a better way to handle this -#if defined(_WIN32) - Sleep(500); -#elif defined(__linux__) || (defined(__APPLE__) && defined(TARGET_OS_MAC)) - usleep(500000); -#endif + char c = '\0'; + std::string line; - serial_handler = asio::serial_port(io_context); - - std::string serial_device = ""; - - // use user-configured serial device if available, fallback to first available - if (cfgLoadStr("input", "DreamcastControllerUsbSerialDevice", "default") != "default") { - serial_device = cfgLoadStr("input", "DreamcastControllerUsbSerialDevice", "default"); - INFO_LOG(INPUT, "DreamcastController[%d] connecting to user-configured serial device: %s", bus, serial_device.c_str()); - } - else + // Consume characters until buffers are empty or \n found + asio::const_buffers_1 data = serial_read_buffer.data(); + std::size_t consumed = 0; + for (const asio::const_buffer& buff : data) { - serial_device = getFirstSerialDevice(); - INFO_LOG(INPUT, "DreamcastController[%d] connecting to autoselected serial device: %s", bus, serial_device.c_str()); + const char* buffDat = static_cast(buff.data()); + for (std::size_t i = 0; i < buff.size(); ++i) + { + c = *buffDat++; + ++consumed; + + if (c == '\n') { + // Stop reading now + break; + } + + line += c; + } + + if (c == '\n') { + // Stop reading now + break; + } } - serial_handler.open(serial_device, ec); - - if (ec || !serial_handler.is_open()) { - WARN_LOG(INPUT, "DreamcastController[%d] connection failed: %s", bus, ec.message().c_str()); - disconnect(); - return; + if (c == '\n') { + serial_read_buffer.consume(consumed); + + // Remove carriage return if found and add this line to queue + if (line.size() > 0 && line[line.size() - 1] == '\r') { + line.pop_back(); + } + read_queue.push_back(std::move(line)); + + ++numberOfLines; + } + else { + // Ran out of data to consume + return numberOfLines; } - break; - } - default: - { - return; } } - - // Now get the controller configuration - MapleMsg msg; - msg.command = MDCF_GetCondition; - msg.destAP = (bus << 6) | 0x20; - msg.originAP = bus << 6; - msg.setData(MFID_0_Input); - - ec = sendMsg(msg, iostream, io_context, serial_handler, dreamcastControllerType); - if (ec) +}; + +DreamConn::DreamConn(int bus, int dreamcastControllerType) : bus(bus), dreamcastControllerType(dreamcastControllerType) { + switch (dreamcastControllerType) { - WARN_LOG(INPUT, "DreamcastController[%d] connection failed: %s", bus, ec.message().c_str()); - disconnect(); + case TYPE_DREAMCONN: + dcConnection = std::make_unique(bus); + break; + + case TYPE_DREAMCASTCONTROLLERUSB: + dcConnection = std::make_unique(bus); + break; + } + + connect(); +} + +DreamConn::~DreamConn() { + disconnect(); +} + +void DreamConn::connect() +{ + maple_io_connected = false; + expansionDevs = 0; + + if (!dcConnection) { return; } - if (!receiveMsg(msg, iostream, serial_handler, dreamcastControllerType)) { - WARN_LOG(INPUT, "DreamcastController[%d] read timeout", bus); - disconnect(); + + std::optional msg = dcConnection->connect(); + if (!msg) + { return; } - if (dreamcastControllerType == TYPE_DREAMCONN) - iostream.expires_from_now(std::chrono::duration::max()); // don't use a 64-bit based duration to avoid overflow - expansionDevs = msg.originAP & 0x1f; - + expansionDevs = msg->originAP & 0x1f; + config::MapleExpansionDevices[bus][0] = hasVmu() ? MDT_SegaVMU : MDT_None; config::MapleExpansionDevices[bus][1] = hasRumble() ? MDT_PurupuruPack : MDT_None; - + if (hasVmu() || hasRumble()) { NOTICE_LOG(INPUT, "Connected to DreamcastController[%d]: Type:%s, VMU:%d, Rumble Pack:%d", bus, dreamcastControllerType == 1 ? "DreamConn+ / DreamcConn S Controller" : "Dreamcast Controller USB", hasVmu(), hasRumble()); @@ -303,30 +620,27 @@ void DreamConn::connect() void DreamConn::disconnect() { - if (dreamcastControllerType == TYPE_DREAMCONN) - { - if (iostream) - iostream.close(); - } - else if (dreamcastControllerType == TYPE_DREAMCASTCONTROLLERUSB) - { - if (serial_handler.is_open()) - serial_handler.cancel(); - serial_handler.close(); - io_context.stop(); + if (!dcConnection) { + return; } - + + dcConnection->disconnect(); + maple_io_connected = false; - + NOTICE_LOG(INPUT, "Disconnected from DreamcastController[%d]", bus); } bool DreamConn::send(const MapleMsg& msg) { + if (!dcConnection) { + return false; + } + asio::error_code ec; if (maple_io_connected) - ec = sendMsg(msg, iostream, io_context, serial_handler, dreamcastControllerType); + ec = dcConnection->sendMsg(msg); else return false; if (ec) { @@ -342,14 +656,16 @@ bool DreamConnGamepad::isDreamcastController(int deviceIndex) { char guid_str[33] {}; SDL_JoystickGetGUIDString(SDL_JoystickGetDeviceGUID(deviceIndex), guid_str, sizeof(guid_str)); - INFO_LOG(INPUT, "GUID: %s VID:%c%c%c%c PID:%c%c%c%c", guid_str, + NOTICE_LOG(INPUT, "GUID: %s VID:%c%c%c%c PID:%c%c%c%c", guid_str, guid_str[10], guid_str[11], guid_str[8], guid_str[9], guid_str[18], guid_str[19], guid_str[16], guid_str[17]); - + // DreamConn VID:4457 PID:4443 // Dreamcast Controller USB VID:1209 PID:2f07 - if (memcmp("5744000043440000", guid_str + 8, 16) == 0 || memcmp("09120000072f0000", guid_str + 8, 16) == 0) + if (memcmp(DreamConnConnection::VID_PID_GUID, guid_str + 8, 16) == 0 || + memcmp(DreamcastControllerUsbPicoConnection::VID_PID_GUID, guid_str + 8, 16) == 0) { + NOTICE_LOG(INPUT, "Dreamcast controller found!"); return true; } return false; @@ -359,22 +675,22 @@ DreamConnGamepad::DreamConnGamepad(int maple_port, int joystick_idx, SDL_Joystic : SDLGamepad(maple_port, joystick_idx, sdl_joystick) { char guid_str[33] {}; - + SDL_JoystickGetGUIDString(SDL_JoystickGetDeviceGUID(joystick_idx), guid_str, sizeof(guid_str)); - + // DreamConn VID:4457 PID:4443 // Dreamcast Controller USB VID:1209 PID:2f07 - if (memcmp("5744000043440000", guid_str + 8, 16) == 0) + if (memcmp(DreamConnConnection::VID_PID_GUID, guid_str + 8, 16) == 0) { dreamcastControllerType = TYPE_DREAMCONN; _name = "DreamConn+ / DreamConn S Controller"; } - else if (memcmp("09120000072f0000", guid_str + 8, 16) == 0) + else if (memcmp(DreamcastControllerUsbPicoConnection::VID_PID_GUID, guid_str + 8, 16) == 0) { dreamcastControllerType = TYPE_DREAMCASTCONTROLLERUSB; _name = "Dreamcast Controller USB"; } - + EventManager::listen(Event::Start, handleEvent, this); EventManager::listen(Event::LoadState, handleEvent, this); } @@ -444,7 +760,7 @@ void DreamConnGamepad::checkKeyCombo() { gui_open_settings(); } -#else +#else // USE_DREAMCASTCONTROLLER void DreamConn::connect() { } diff --git a/core/sdl/dreamconn.h b/core/sdl/dreamconn.h index d883c61fc..90200615a 100644 --- a/core/sdl/dreamconn.h +++ b/core/sdl/dreamconn.h @@ -26,6 +26,7 @@ #define TYPE_DREAMCASTCONTROLLERUSB 2 #include #endif +#include struct MapleMsg { @@ -52,21 +53,15 @@ class DreamConn const int bus; const int dreamcastControllerType; #ifdef USE_DREAMCASTCONTROLLER - asio::ip::tcp::iostream iostream; - asio::io_context io_context; - asio::serial_port serial_handler{io_context}; + std::unique_ptr dcConnection; #endif bool maple_io_connected; u8 expansionDevs = 0; - static constexpr u16 BASE_PORT = 37393; public: - DreamConn(int bus, int dreamcastControllerType) : bus(bus), dreamcastControllerType(dreamcastControllerType) { - connect(); - } - ~DreamConn() { - disconnect(); - } + DreamConn(int bus, int dreamcastControllerType); + + ~DreamConn(); bool send(const MapleMsg& msg); diff --git a/core/sdl/sdl.cpp b/core/sdl/sdl.cpp index 44d269507..794c2f315 100644 --- a/core/sdl/sdl.cpp +++ b/core/sdl/sdl.cpp @@ -271,9 +271,9 @@ void input_sdl_init() // Linux mappings are OK by default // Can be removed once mapping is merged into SDL, see https://github.com/libsdl-org/SDL/pull/12039 #if (defined(__APPLE__) && defined(TARGET_OS_MAC)) - SDL_GameControllerAddMapping("0300000009120000072f000000010000,OrangeFox86 Dreamcast Controller USB,crc:3cef,a:b0,b:b1,x:b3,y:b4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,dpdown:h0.4,leftx:a0,lefty:a1,lefttrigger:a2,righttrigger:a5,start:b11"); + SDL_GameControllerAddMapping("0300000009120000072f000000010000,OrangeFox86 Dreamcast Controller USB,a:b0,b:b1,x:b3,y:b4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,dpdown:h0.4,leftx:a0,lefty:a1,lefttrigger:a2,righttrigger:a5,start:b11"); #elif defined(_WIN32) - SDL_GameControllerAddMapping("0300000009120000072f000000000000,OrangeFox86 Dreamcast Controller USB,crc:baa5,a:b0,b:b1,x:b3,y:b4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,dpdown:h0.4,leftx:a0,lefty:a1,lefttrigger:-a2,righttrigger:-a5,start:b11"); + SDL_GameControllerAddMapping("0300000009120000072f000000000000,OrangeFox86 Dreamcast Controller USB,a:b0,b:b1,x:b3,y:b4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,dpdown:h0.4,leftx:a0,lefty:a1,lefttrigger:-a2,righttrigger:-a5,start:b11"); #endif } @@ -547,7 +547,7 @@ void input_sdl_handle() case SDL_JOYDEVICEREMOVED: sdl_close_joystick((SDL_JoystickID)event.jdevice.which); break; - + case SDL_DROPFILE: gui_start_game(event.drop.file); break; @@ -602,7 +602,7 @@ static inline void get_window_state() windowPos.h /= hdpiScaling; SDL_GetWindowPosition(window, &windowPos.x, &windowPos.y); } - + } #if defined(_WIN32) && !defined(TARGET_UWP) @@ -629,14 +629,14 @@ bool sdl_recreate_window(u32 flags) PROCESS_SYSTEM_DPI_AWARE = 1, PROCESS_PER_MONITOR_DPI_AWARE = 2 } PROCESS_DPI_AWARENESS; - + HRESULT(WINAPI *SetProcessDpiAwareness)(PROCESS_DPI_AWARENESS dpiAwareness); // Windows 8.1 and later void* shcoreDLL = SDL_LoadObject("SHCORE.DLL"); if (shcoreDLL) { SetProcessDpiAwareness = (HRESULT(WINAPI *)(PROCESS_DPI_AWARENESS)) SDL_LoadFunction(shcoreDLL, "SetProcessDpiAwareness"); if (SetProcessDpiAwareness) { SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE); - + if (SDL_GetDisplayDPI(0, &settings.display.dpi, NULL, NULL) != -1){ //SDL_WINDOWPOS_UNDEFINED is Display 0 //When using HiDPI mode, set correct DPI scaling hdpiScaling = settings.display.dpi / 96.f; @@ -645,7 +645,7 @@ bool sdl_recreate_window(u32 flags) SDL_UnloadObject(shcoreDLL); } #endif - + #ifdef __SWITCH__ AppletOperationMode om = appletGetOperationMode(); if (om == AppletOperationMode_Handheld)