mirror of
https://github.com/flyinghead/flycast.git
synced 2026-11-09 01:59:49 -10:00
237 lines
5.8 KiB
C++
237 lines
5.8 KiB
C++
/*
|
|
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 "types.h"
|
|
#include <asio.hpp> // Must be included first to avoid winsock issues on Windows
|
|
#include "dreampotato.h"
|
|
#include "maplelink.h"
|
|
#include "emulator.h"
|
|
#include "cfg/option.h"
|
|
#include <memory>
|
|
#include <array>
|
|
#include <iomanip>
|
|
|
|
asio::error_code sendMsg(const MapleMsg& msg, asio::ip::tcp::iostream& stream)
|
|
{
|
|
static char buffer[1024 * 3 + 2];
|
|
char *p = buffer;
|
|
p += sprintf(p, "%02X %02X %02X %02X", msg.command, msg.destAP, msg.originAP, msg.size);
|
|
const u32 sz = msg.getDataSize();
|
|
for (u32 i = 0; i < sz; i++)
|
|
p += sprintf(p, " %02X", msg.data[i]);
|
|
strcpy(p, "\r\n");
|
|
p += 2;
|
|
asio::ip::tcp::socket& sock = static_cast<asio::ip::tcp::socket&>(stream.socket());
|
|
asio::error_code ec;
|
|
asio::write(sock, asio::buffer(buffer, p - buffer), ec);
|
|
return ec;
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
namespace dreampotato
|
|
{
|
|
|
|
class DreamPotato : public BaseMapleLink
|
|
{
|
|
int bus;
|
|
asio::ip::tcp::iostream iostream;
|
|
bool connected = false;
|
|
static constexpr u16 BASE_PORT = 37393;
|
|
time_t lastMsg = 0;
|
|
|
|
public:
|
|
DreamPotato(int bus)
|
|
: BaseMapleLink(true), bus(bus)
|
|
{}
|
|
|
|
bool send(const MapleMsg& msg) override;
|
|
bool sendReceive(const MapleMsg& txMsg, MapleMsg& rxMsg) override;
|
|
|
|
bool isConnected() override {
|
|
checkConnection();
|
|
return connected;
|
|
}
|
|
|
|
void connect();
|
|
void disconnect();
|
|
void init();
|
|
void term();
|
|
|
|
void checkConnection()
|
|
{
|
|
if (!connected)
|
|
return;
|
|
time_t now = getTimeMs();
|
|
if (now - lastMsg < 1000)
|
|
return;
|
|
|
|
lastMsg = now;
|
|
asio::ip::tcp::socket& sock = static_cast<asio::ip::tcp::socket&>(iostream.socket());
|
|
bool nonBlocking = sock.non_blocking();
|
|
std::error_code ec;
|
|
if (!nonBlocking)
|
|
sock.non_blocking(true, ec);
|
|
// We don't expect to have anything to read
|
|
u8 buf;
|
|
size_t read = sock.read_some(asio::buffer(&buf, 1), ec);
|
|
if (!nonBlocking) {
|
|
std::error_code ignored;
|
|
sock.non_blocking(false, ignored);
|
|
}
|
|
if (read != 1 && !!ec && ec != asio::error::would_block)
|
|
disconnect();
|
|
}
|
|
};
|
|
|
|
static std::array<std::shared_ptr<DreamPotato>, 4> Potatoes;
|
|
|
|
void DreamPotato::init()
|
|
{
|
|
connect();
|
|
registerLink(bus, 0);
|
|
}
|
|
|
|
void DreamPotato::term()
|
|
{
|
|
disconnect();
|
|
unregisterLink(bus, 0);
|
|
}
|
|
|
|
void DreamPotato::connect()
|
|
{
|
|
if (connected)
|
|
return;
|
|
iostream = asio::ip::tcp::iostream("::1", std::to_string(BASE_PORT + bus), asio::ip::resolver_base::flags::numeric_host);
|
|
if (!iostream)
|
|
iostream = asio::ip::tcp::iostream("127.0.0.1", std::to_string(BASE_PORT + bus), asio::ip::resolver_base::flags::numeric_host);
|
|
if (!iostream) {
|
|
WARN_LOG(INPUT, "DreamPotato[%d] connection failed: %s", bus, iostream.error().message().c_str());
|
|
disconnect();
|
|
return;
|
|
}
|
|
connected = true;
|
|
iostream.expires_from_now(std::chrono::milliseconds(500));
|
|
|
|
MapleMsg txMsg;
|
|
txMsg.command = MDC_DeviceRequest;
|
|
txMsg.destAP = (bus << 6) | 1;
|
|
txMsg.originAP = bus << 6;
|
|
MapleMsg rxMsg;
|
|
// Note: connected *must* be true or infinite recursion loop
|
|
if (!sendReceive(txMsg, rxMsg))
|
|
// error has been logged and disconnect() called
|
|
return;
|
|
const u32 fnCode = rxMsg.readData<u32>();
|
|
if ((fnCode & MFID_1_Storage) == 0)
|
|
{
|
|
WARN_LOG(INPUT, "Unrecognized response from DreamPotato");
|
|
disconnect();
|
|
return;
|
|
}
|
|
|
|
NOTICE_LOG(INPUT, "Connected to DreamPotato[%d]", bus);
|
|
}
|
|
|
|
void DreamPotato::disconnect()
|
|
{
|
|
if (!connected)
|
|
return;
|
|
if (storageEnabled())
|
|
disableStorage();
|
|
connected = false;
|
|
iostream.close();
|
|
NOTICE_LOG(INPUT, "Disconnected from DreamPotato[%d]", bus);
|
|
}
|
|
|
|
bool DreamPotato::send(const MapleMsg& msg)
|
|
{
|
|
connect();
|
|
if (!connected)
|
|
return false;
|
|
|
|
iostream.expires_from_now(std::chrono::milliseconds(100));
|
|
asio::error_code ec = sendMsg(msg, iostream);
|
|
if (ec)
|
|
{
|
|
WARN_LOG(INPUT, "DreamPotato[%d] send failed: %s", bus, ec.message().c_str());
|
|
disconnect();
|
|
return false;
|
|
}
|
|
lastMsg = getTimeMs();
|
|
return true;
|
|
}
|
|
|
|
bool DreamPotato::sendReceive(const MapleMsg& txMsg, MapleMsg& rxMsg)
|
|
{
|
|
if (!send(txMsg))
|
|
return false;
|
|
|
|
if (!receiveMsg(rxMsg, iostream)) {
|
|
WARN_LOG(INPUT, "DreamPotato[%d] receive failed", bus);
|
|
disconnect();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Instantiate DreamPotato devices where needed and delete the others
|
|
void update()
|
|
{
|
|
for (unsigned bus = 0; bus < Potatoes.size(); bus++)
|
|
{
|
|
auto& potato = Potatoes[bus];
|
|
if (config::NetworkExpansionDevices[bus][0] == 1
|
|
&& config::MapleExpansionDevices[bus][0] == MDT_SegaVMU
|
|
&& maple_getPortCount(config::MapleMainDevices[bus], MaplePortType::built_in) >= 1)
|
|
{
|
|
if (potato == nullptr) {
|
|
potato = std::make_shared<DreamPotato>(bus);
|
|
potato->init();
|
|
}
|
|
}
|
|
else if (potato != nullptr) {
|
|
potato->term();
|
|
potato = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
void term()
|
|
{
|
|
for (unsigned bus = 0; bus < Potatoes.size(); bus++)
|
|
{
|
|
auto& potato = Potatoes[bus];
|
|
if (potato != nullptr) {
|
|
potato->term();
|
|
potato = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace dreampotato
|