/*
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"
#ifdef USE_DREAMCASTCONTROLLER
#include "hw/maple/maple_devs.h"
#include "ui/gui.h"
#include
#include
#include
#include
#include
#if defined(__linux__) || (defined(__APPLE__) && defined(TARGET_OS_MAC))
#include
#endif
#if defined(_WIN32)
#include
#include
#endif
static asio::error_code sendMsg(const MapleMsg& msg, asio::ip::tcp::iostream& stream)
{
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";
asio::ip::tcp::socket& sock = static_cast(stream.socket());
asio::error_code ec;
asio::write(sock, asio::buffer(s.str()), ec);
return ec;
}
static bool receiveMsg(MapleMsg& msg, std::istream& stream)
{
std::string response;
if (!std::getline(stream, 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();
}
DreamConn::DreamConn(int bus) : bus(bus) {
}
DreamConn::~DreamConn() {
disconnect();
}
bool DreamConn::send(const MapleMsg& msg) {
std::lock_guard lock(send_mutex); // Ensure thread safety for send operations
return send_no_lock(msg);
}
bool DreamConn::send_no_lock(const MapleMsg& msg) {
asio::error_code ec;
if (maple_io_connected)
ec = sendMsg(msg, iostream);
else
return false;
if (ec) {
maple_io_connected = false;
WARN_LOG(INPUT, "DreamcastController[%d] send failed: %s", bus, ec.message().c_str());
disconnect();
return false;
}
return true;
}
bool DreamConn::send(const MapleMsg& txMsg, MapleMsg& rxMsg) {
std::lock_guard lock(send_mutex); // Ensure thread safety for send operations
if (!send_no_lock(txMsg)) {
return false;
}
return receiveMsg(rxMsg, iostream);
}
void DreamConn::changeBus(int newBus) {
bus = newBus;
}
void DreamConn::connect() {
maple_io_connected = false;
asio::error_code ec;
#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;
}
iostream.expires_from_now(std::chrono::seconds(1));
// 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);
if (ec)
{
WARN_LOG(INPUT, "DreamcastController[%d] connection failed: %s", bus, ec.message().c_str());
disconnect();
return;
}
if (!receiveMsg(msg, iostream)) {
WARN_LOG(INPUT, "DreamcastController[%d] read timeout", bus);
disconnect();
return;
}
iostream.expires_from_now(std::chrono::duration::max()); // don't use a 64-bit based duration to avoid overflow
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, getName().c_str(), hasVmu(), hasRumble());
maple_io_connected = true;
}
else
{
WARN_LOG(INPUT, "DreamcastController[%d] connection: no VMU or Rumble Pack connected", bus);
disconnect();
return;
}
}
void DreamConn::disconnect() {
if (iostream)
iostream.close();
maple_io_connected = false;
NOTICE_LOG(INPUT, "Disconnected from DreamcastController[%d]", bus);
}
#endif