Files

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