Skip to content

Commit

Permalink
input: dreamconn+ support prototype
Browse files Browse the repository at this point in the history
Issue #1305
  • Loading branch information
flyinghead committed Dec 16, 2024
1 parent 3f8d645 commit 34a5e7f
Show file tree
Hide file tree
Showing 5 changed files with 268 additions and 1 deletion.
9 changes: 8 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
67 changes: 67 additions & 0 deletions core/hw/maple/maple_devs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<DreamConnVmu*>(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<DreamConnPurupuru*>(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
127 changes: 127 additions & 0 deletions core/sdl/dreamconn.cpp
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.
*/
#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<typename T>
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<DreamConn*>(arg));
}

#else

void DreamConn::connect() {
}
void DreamConn::disconnect() {
}

#endif
56 changes: 56 additions & 0 deletions core/sdl/dreamconn.h
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.
*/
#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);
};
10 changes: 10 additions & 0 deletions core/sdl/sdl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include "nswitch.h"
#include "switch_gamepad.h"
#endif
#include "dreamconn.h"
#include <unordered_map>

static SDL_Window* window = NULL;
Expand All @@ -48,6 +49,7 @@ static bool mouseCaptured;
static std::string clipboardText;
static std::string barcode;
static u64 lastBarcodeTime;
static std::unique_ptr<DreamConn> dreamconns[4];

static KeyboardLayout detectKeyboardLayout();
static bool handleBarcodeScanner(const SDL_Event& event);
Expand Down Expand Up @@ -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<DreamConn>(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);
Expand Down

0 comments on commit 34a5e7f

Please sign in to comment.