diff --git a/CMakeLists.txt b/CMakeLists.txt index b9f267fa0..76f9e082e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -461,7 +461,14 @@ if(NOT LIBRETRO) endif() target_compile_definitions(${PROJECT_NAME} PRIVATE USE_SDL USE_SDL_AUDIO) - target_sources(${PROJECT_NAME} PRIVATE core/sdl/sdl.cpp core/sdl/sdl.h core/sdl/sdl_gamepad.h core/sdl/sdl_keyboard.h) + target_sources(${PROJECT_NAME} PRIVATE + core/sdl/sdl.cpp + core/sdl/sdl.h + core/sdl/sdl_gamepad.h + core/sdl/sdl_keyboard.h + core/sdl/sdl_keyboard_mac.h + core/sdl/dreamconn.cpp + core/sdl/dreamconn.h) if((UNIX AND NOT APPLE) OR NINTENDO_SWITCH) find_package(CURL REQUIRED) diff --git a/core/hw/maple/maple_devs.cpp b/core/hw/maple/maple_devs.cpp index 9e2eb242c..b0cfcf84b 100755 --- a/core/hw/maple/maple_devs.cpp +++ b/core/hw/maple/maple_devs.cpp @@ -2101,3 +2101,70 @@ maple_device* maple_Create(MapleDeviceType type) } return nullptr; } + +#if defined(_WIN32) && !defined(TARGET_UWP) && defined(USE_SDL) && !defined(LIBRETRO) +#include "sdl/dreamconn.h" + +struct DreamConnVmu : public maple_sega_vmu +{ + DreamConn& dreamconn; + + DreamConnVmu(DreamConn& dreamconn) : dreamconn(dreamconn) { + } + + u32 dma(u32 cmd) override + { + if (cmd == MDCF_BlockWrite && *(u32 *)dma_buffer_in == MFID_2_LCD) + // send the raw maple msg + dreamconn.send(dma_buffer_in - 4, dma_count_in + 4); + return maple_sega_vmu::dma(cmd); + } +}; + +struct DreamConnPurupuru : public maple_sega_purupuru +{ + DreamConn& dreamconn; + + DreamConnPurupuru(DreamConn& dreamconn) : dreamconn(dreamconn) { + } + + u32 dma(u32 cmd) override + { + switch (cmd) + { + case MDCF_BlockWrite: + dreamconn.send(dma_buffer_in - 4, dma_count_in + 4); + break; + + case MDCF_SetCondition: + dreamconn.send(dma_buffer_in - 4, dma_count_in + 4); + break; + } + return maple_sega_purupuru::dma(cmd); + } +}; + +void createDreamConnDevices(DreamConn& dreamconn) +{ + const int bus = dreamconn.getBus(); + if (dreamconn.hasVmu() && dynamic_cast(MapleDevices[bus][0]) == nullptr) + { + delete MapleDevices[bus][0]; + DreamConnVmu *dev = new DreamConnVmu(dreamconn); + dev->Setup((bus << 6) | 1); + dev->config = new MapleConfigMap(dev); + dev->OnSetup(); + MapleDevices[bus][0] = dev; + } + if (dreamconn.hasRumble() && dynamic_cast(MapleDevices[bus][1]) == nullptr) + { + delete MapleDevices[bus][1]; + DreamConnPurupuru *dev = new DreamConnPurupuru(dreamconn); + dev->Setup((bus << 6) | 2); + dev->config = new MapleConfigMap(dev); + dev->OnSetup(); + MapleDevices[bus][1] = dev; + } +} + +#endif diff --git a/core/sdl/dreamconn.cpp b/core/sdl/dreamconn.cpp new file mode 100644 index 000000000..34a36adad --- /dev/null +++ b/core/sdl/dreamconn.cpp @@ -0,0 +1,127 @@ +/* + Copyright 2024 flyinghead + + This file is part of Flycast. + + Flycast is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + Flycast is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Flycast. If not, see . + */ +#include "dreamconn.h" + +#if defined(_WIN32) && !defined(TARGET_UWP) +#include "hw/maple/maple_devs.h" + +void createDreamConnDevices(DreamConn& dreamconn); + +struct MapleMsg +{ + u8 command; + u8 destAP; + u8 originAP; + u8 size; + u8 data[1024]; + + u32 getDataSize() const { + return size * 4; + } + + template + void setData(const T& p) { + memcpy(data, &p, sizeof(T)); + this->size = (sizeof(T) + 3) / 4; + } + + bool send(sock_t sock) { + u32 sz = getDataSize() + 4; + return ::write(sock, this, sz) == sz; + } + bool receive(sock_t sock) + { + if (::read(sock, this, 4) != 4) + return false; + if (getDataSize() == 0) + return true; + return ::read(sock, data, getDataSize()) == getDataSize(); + } +}; +static_assert(sizeof(MapleMsg) == 1028); + +void DreamConn::connect() +{ + sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (!VALID(sock)) + return; + set_recv_timeout(sock, 1000); + sockaddr_in addr {}; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + addr.sin_port = htons(BASE_PORT + bus); + if (::connect(sock, (sockaddr *)&addr, sizeof(addr)) != 0) + { + WARN_LOG(INPUT, "DreamConn[%d] connection failed", bus); + disconnect(); + 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); + if (!msg.send(sock)) + { + WARN_LOG(INPUT, "DreamConn[%d] communication failed", bus); + disconnect(); + return; + } + if (!msg.receive(sock)) { + WARN_LOG(INPUT, "DreamConn[%d] read timeout", bus); + disconnect(); + return; + } + expansionDevs = msg.originAP & 0x1f; + NOTICE_LOG(INPUT, "Connected to DreamConn[%d]: VMU:%d, Rumble Pack:%d", bus, hasVmu(), hasRumble()); + + EventManager::listen(Event::Resume, handleEvent, this); +} + +void DreamConn::disconnect() +{ + EventManager::unlisten(Event::Resume, handleEvent, this); + if (VALID(sock)) { + NOTICE_LOG(INPUT, "Disconnected from DreamConn[%d]", bus); + closesocket(sock); + } + sock = INVALID_SOCKET; +} + +bool DreamConn::send(const u8* data, int size) +{ + if (VALID(sock)) + return write(sock, data, size) == size; + else + return false; +} + +void DreamConn::handleEvent(Event event, void *arg) { + createDreamConnDevices(*static_cast(arg)); +} + +#else + +void DreamConn::connect() { +} +void DreamConn::disconnect() { +} + +#endif diff --git a/core/sdl/dreamconn.h b/core/sdl/dreamconn.h new file mode 100644 index 000000000..60bd56cf8 --- /dev/null +++ b/core/sdl/dreamconn.h @@ -0,0 +1,56 @@ +/* + Copyright 2024 flyinghead + + This file is part of Flycast. + + Flycast is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + Flycast is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Flycast. If not, see . + */ +#pragma once +#include "types.h" +#include "network/net_platform.h" +#include "emulator.h" + +// TODO Need a way to detect DreamConn+ controllers +class DreamConn +{ + const int bus; + sock_t sock = INVALID_SOCKET; + u8 expansionDevs = 0; + static constexpr u16 BASE_PORT = 37393; + +public: + DreamConn(int bus) : bus(bus) { + connect(); + } + ~DreamConn() { + disconnect(); + } + + bool send(const u8* data, int size); + + int getBus() const { + return bus; + } + bool hasVmu() { + return expansionDevs & 1; + } + bool hasRumble() { + return expansionDevs & 2; + } + +private: + void connect(); + void disconnect(); + static void handleEvent(Event event, void *arg); +}; diff --git a/core/sdl/sdl.cpp b/core/sdl/sdl.cpp index 9027dfa25..7f77e452f 100644 --- a/core/sdl/sdl.cpp +++ b/core/sdl/sdl.cpp @@ -30,6 +30,7 @@ #include "nswitch.h" #include "switch_gamepad.h" #endif +#include "dreamconn.h" #include static SDL_Window* window = NULL; @@ -48,6 +49,7 @@ static bool mouseCaptured; static std::string clipboardText; static std::string barcode; static u64 lastBarcodeTime; +static std::unique_ptr dreamconns[4]; static KeyboardLayout detectKeyboardLayout(); static bool handleBarcodeScanner(const SDL_Event& event); @@ -260,10 +262,18 @@ void input_sdl_init() if (settings.input.keyboardLangId == KeyboardLayout::US) settings.input.keyboardLangId = detectKeyboardLayout(); barcode.clear(); + for (unsigned i = 0; i < std::size(dreamconns); i++) + { + std::string key = "DreamConn" + std::to_string(i); + if (cfgLoadBool("input", key.c_str(), false)) + dreamconns[i] = std::make_unique(i); + } } void input_sdl_quit() { + for (auto& dc : dreamconns) + dc.reset(); EventManager::unlisten(Event::Terminate, emuEventCallback); EventManager::unlisten(Event::Pause, emuEventCallback); EventManager::unlisten(Event::Resume, emuEventCallback);