flycast/core/network/picoppp.cpp

1117 lines
28 KiB
C++

/*
Created on: Sep 15, 2018
Copyright 2018 flyinghead
This file is part of reicast.
reicast 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.
reicast 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 reicast. If not, see <https://www.gnu.org/licenses/>.
*/
#ifdef _WIN32
#include <ws2tcpip.h>
#endif
#include "stdclass.h"
#include "oslib/oslib.h"
#ifdef __MINGW32__
#define _POSIX_SOURCE
#endif
extern "C" {
#include <pico_stack.h>
#include <pico_dev_ppp.h>
#include <pico_socket.h>
#include <pico_socket_tcp.h>
#include <pico_ipv4.h>
#include <pico_tcp.h>
#include <pico_dhcp_server.h>
}
#include "net_platform.h"
#include "types.h"
#include "cfg/cfg.h"
#include "picoppp.h"
#include "miniupnp.h"
#include "reios/reios.h"
#include "hw/naomi/naomi_cart.h"
#include "cfg/option.h"
#include "emulator.h"
#include <map>
#include <mutex>
#include <queue>
#include <future>
#define RESOLVER1_OPENDNS_COM "208.67.222.222"
#define AFO_ORIG_IP 0x83f2fb3f // 63.251.242.131 in network order
#define IGP_ORIG_IP 0xef2bd2cc // 204.210.43.239 in network order
static pico_device *pico_dev;
static std::queue<u8> in_buffer;
static std::queue<u8> out_buffer;
static std::mutex in_buffer_lock;
static std::mutex out_buffer_lock;
static pico_ip4 dcaddr;
static pico_ip4 dnsaddr;
static pico_socket *pico_tcp_socket, *pico_udp_socket;
static pico_ip4 afo_ip;
struct socket_pair
{
socket_pair() : pico_sock(nullptr), native_sock(INVALID_SOCKET) {}
socket_pair(pico_socket *pico_sock, sock_t native_sock) : pico_sock(pico_sock), native_sock(native_sock) {}
~socket_pair() {
if (pico_sock != nullptr)
pico_socket_close(pico_sock);
if (native_sock != INVALID_SOCKET)
closesocket(native_sock);
}
socket_pair(socket_pair &&) = default;
socket_pair(const socket_pair&) = delete;
socket_pair& operator=(const socket_pair&) = delete;
pico_socket *pico_sock;
sock_t native_sock;
std::vector<char> in_buffer;
bool shutdown = false;
void receive_native()
{
size_t len;
const char *data;
char buf[536];
if (!in_buffer.empty())
{
len = in_buffer.size();
data = &in_buffer[0];
}
else
{
if (native_sock == INVALID_SOCKET)
{
if (!shutdown && pico_sock->q_out.size == 0)
{
pico_socket_shutdown(pico_sock, PICO_SHUT_RDWR);
shutdown = true;
}
return;
}
int r = (int)recv(native_sock, buf, sizeof(buf), 0);
if (r == 0)
{
INFO_LOG(MODEM, "Socket[%d] recv(%zd) returned 0 -> EOF", short_be(pico_sock->remote_port), sizeof(buf));
closesocket(native_sock);
native_sock = INVALID_SOCKET;
return;
}
if (r < 0)
{
if (get_last_error() != L_EAGAIN && get_last_error() != L_EWOULDBLOCK)
{
perror("recv tcp socket");
closesocket(native_sock);
native_sock = INVALID_SOCKET;
}
return;
}
len = r;
data = buf;
}
if (pico_sock->remote_port == short_be(5011) && len >= 5)
{
// Visual Concepts sport games
if (buf[0] == 1)
memcpy(&buf[1], &pico_sock->local_addr.ip4.addr, 4);
}
int r2 = pico_socket_send(pico_sock, data, (int)len);
if (r2 < 0)
INFO_LOG(MODEM, "error TCP sending: %s", strerror(pico_err));
else if (r2 < (int)len)
{
if (r2 > 0 || in_buffer.empty())
{
len -= r2;
std::vector<char> remain(len);
memcpy(&remain[0], &data[r2], len);
std::swap(in_buffer, remain);
}
}
else
{
in_buffer.clear();
}
}
};
// tcp sockets
static std::map<pico_socket *, socket_pair> tcp_sockets;
static std::map<pico_socket *, sock_t> tcp_connecting_sockets;
// udp sockets: src port -> socket fd
static std::map<uint16_t, sock_t> udp_sockets;
struct GamePortList {
const char *gameId[10];
uint16_t udpPorts[10];
uint16_t tcpPorts[10];
};
static GamePortList GamesPorts[] = {
{ // Alien Front Online
{ "MK-51171" },
{ 7980 },
{ },
},
{ // ChuChu Rocket
{ "MK-51049", "HDR-0039", "MK-5104950" },
{ 9789 },
{ },
},
{ // NBA 2K1,2K2 / NFL 2K1,2K2 / NCAA 2K2
{ "MK-51063", "HDR-0150", // NBA 2K1
"MK-51178", "HDR-0197", "MK-5117850", // NBA 2K2
"MK-51062", "HDR-0144", // NFL 2K1
"MK-51168", "HDR-0196", // NFL 2K2
"MK-51176" }, // NCAA 2K2
{ 5502, 5503, 5656 },
{ 5011, 6666 },
},
{ // The Next Tetris
{ "T40214N", "T17717D 50" },
{ 3512 },
{ 3512 },
},
{ // Ooga Booga
{ "MK-51140" },
{ 6001 },
{ },
},
{ // PBA Tour Bowling 2001
{ "T26702N" },
{ 2300, 6500, 47624, 13139 }, // FIXME 2300-2400 ?
{ 2300, 47624 }, // FIXME 2300-2400 ?
},
{ // Planet Ring
{ "MK-5114864", "MK-5112550" },
{ 7648, 1285, 1028 },
{ },
},
{ // StarLancer
{ "T40209N", "T17723D 05" },
{ 2300, 6500, 47624 }, // FIXME 2300-2400 ?
{ 2300, 47624 }, // FIXME 2300-2400 ?
},
{ // World Series Baseball 2K2
{ "MK-51152", "HDR-0198" },
{ 37171, 13713 },
{ },
},
{ // Worms World Party
{ "T22904N", "T7016D 50" },
{ },
{ 17219 },
},
{ // Atomiswave
{ "FASTER THAN SPEED" },
{ 8888 },
{ },
},
};
// listening port -> socket fd
static std::map<uint16_t, sock_t> tcp_listening_sockets;
static bool pico_stack_inited;
static bool pico_thread_running = false;
extern "C" int dont_reject_opt_vj_hack;
static void read_native_sockets();
void get_host_by_name(const char *name, pico_ip4 dnsaddr);
int get_dns_answer(pico_ip4 *address, pico_ip4 dnsaddr);
static int modem_read(pico_device *dev, void *data, int len)
{
u8 *p = (u8 *)data;
int count = 0;
out_buffer_lock.lock();
while (!out_buffer.empty() && count < len)
{
*p++ = out_buffer.front();
out_buffer.pop();
count++;
}
out_buffer_lock.unlock();
return count;
}
static int modem_write(pico_device *dev, const void *data, int len)
{
u8 *p = (u8 *)data;
in_buffer_lock.lock();
for (int i = 0; i < len; i++)
{
while (in_buffer.size() > 1024)
{
in_buffer_lock.unlock();
if (!pico_thread_running)
return 0;
PICO_IDLE();
in_buffer_lock.lock();
}
in_buffer.push(*p++);
}
in_buffer_lock.unlock();
return len;
}
void write_pico(u8 b)
{
out_buffer_lock.lock();
out_buffer.push(b);
out_buffer_lock.unlock();
}
int read_pico()
{
in_buffer_lock.lock();
if (in_buffer.empty())
{
in_buffer_lock.unlock();
return -1;
}
else
{
u32 b = in_buffer.front();
in_buffer.pop();
in_buffer_lock.unlock();
return b;
}
}
static void read_from_dc_socket(pico_socket *pico_sock, sock_t nat_sock)
{
char buf[1510];
int r = pico_socket_read(pico_sock, buf, sizeof(buf));
if (r > 0)
{
if (send(nat_sock, buf, r, 0) < r)
{
perror("tcp_callback send");
tcp_sockets.erase(pico_sock);
}
}
}
static void tcp_callback(uint16_t ev, pico_socket *s)
{
if (ev & PICO_SOCK_EV_RD)
{
auto it = tcp_sockets.find(s);
if (it == tcp_sockets.end())
{
if (tcp_connecting_sockets.find(s) == tcp_connecting_sockets.end())
INFO_LOG(MODEM, "Unknown socket: remote port %d", short_be(s->remote_port));
}
else
{
read_from_dc_socket(it->first, it->second.native_sock);
}
}
if (ev & PICO_SOCK_EV_CONN)
{
pico_ip4 orig;
uint16_t port;
char peer[30];
int yes = 1;
pico_socket *sock_a = pico_socket_accept(s, &orig, &port);
if (sock_a == NULL)
{
// Also called for child sockets
if (tcp_sockets.find(s) == tcp_sockets.end())
INFO_LOG(MODEM, "pico_socket_accept: %s\n", strerror(pico_err));
}
else
{
pico_ipv4_to_string(peer, sock_a->local_addr.ip4.addr);
//printf("Connection established from port %d to %s:%d\n", short_be(port), peer, short_be(sock_a->local_port));
pico_socket_setoption(sock_a, PICO_TCP_NODELAY, &yes);
pico_tcp_set_linger(sock_a, 10000);
/* Set keepalive options */
// uint32_t ka_val = 5;
// pico_socket_setoption(sock_a, PICO_SOCKET_OPT_KEEPCNT, &ka_val);
// ka_val = 30000;
// pico_socket_setoption(sock_a, PICO_SOCKET_OPT_KEEPIDLE, &ka_val);
// ka_val = 5000;
// pico_socket_setoption(sock_a, PICO_SOCKET_OPT_KEEPINTVL, &ka_val);
sock_t sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (!VALID(sockfd))
{
perror("socket");
}
else
{
sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = sock_a->local_addr.ip4.addr;
if (serveraddr.sin_addr.s_addr == AFO_ORIG_IP // Alien Front Online
|| serveraddr.sin_addr.s_addr == IGP_ORIG_IP) // Internet Game Pack
{
serveraddr.sin_addr.s_addr = afo_ip.addr; // same ip for both for now
}
serveraddr.sin_port = sock_a->local_port;
set_non_blocking(sockfd);
if (connect(sockfd, (sockaddr *)&serveraddr, sizeof(serveraddr)) < 0)
{
if (get_last_error() != EINPROGRESS && get_last_error() != L_EWOULDBLOCK)
{
pico_ipv4_to_string(peer, sock_a->local_addr.ip4.addr);
INFO_LOG(MODEM, "TCP connection to %s:%d failed: %s", peer, short_be(sock_a->local_port), strerror(get_last_error()));
closesocket(sockfd);
}
else
tcp_connecting_sockets[sock_a] = sockfd;
}
else
{
set_tcp_nodelay(sockfd);
tcp_sockets.emplace(std::piecewise_construct,
std::forward_as_tuple(sock_a),
std::forward_as_tuple(sock_a, sockfd));
}
}
}
}
if (ev & PICO_SOCK_EV_FIN) {
auto it = tcp_sockets.find(s);
if (it != tcp_sockets.end())
{
tcp_sockets.erase(it);
}
else
{
auto it2 = tcp_connecting_sockets.find(s);
if (it2 != tcp_connecting_sockets.end())
{
closesocket(it2->second);
tcp_connecting_sockets.erase(it2);
}
else
INFO_LOG(MODEM, "PICO_SOCK_EV_FIN: Unknown socket: remote port %d", short_be(s->remote_port));
}
}
if (ev & PICO_SOCK_EV_ERR) {
INFO_LOG(MODEM, "Socket error received: %s", strerror(pico_err));
auto it = tcp_sockets.find(s);
if (it == tcp_sockets.end())
INFO_LOG(MODEM, "PICO_SOCK_EV_ERR: Unknown socket: remote port %d", short_be(s->remote_port));
else
tcp_sockets.erase(it);
}
if (ev & PICO_SOCK_EV_CLOSE)
{
auto it = tcp_sockets.find(s);
if (it == tcp_sockets.end())
{
INFO_LOG(MODEM, "PICO_SOCK_EV_CLOSE: Unknown socket: remote port %d", short_be(s->remote_port));
}
else
{
if (it->second.native_sock != INVALID_SOCKET)
shutdown(it->second.native_sock, SHUT_WR);
pico_socket_shutdown(s, PICO_SHUT_RD);
}
}
// if (ev & PICO_SOCK_EV_WR)
// {
// }
}
static sock_t find_udp_socket(uint16_t src_port)
{
auto it = udp_sockets.find(src_port);
if (it != udp_sockets.end())
return it->second;
sock_t sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (!VALID(sockfd))
{
perror("socket");
return -1;
}
#ifndef _WIN32
fcntl(sockfd, F_SETFL, O_NONBLOCK);
#else
u_long optl = 1;
ioctlsocket(sockfd, FIONBIO, &optl);
#endif
int broadcastEnable = 1;
setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, (const char *)&broadcastEnable, sizeof(broadcastEnable));
// FIXME Need to clean up at some point?
udp_sockets[src_port] = sockfd;
return sockfd;
}
static void udp_callback(uint16_t ev, pico_socket *s)
{
if (ev & PICO_SOCK_EV_RD)
{
char buf[1510];
pico_ip4 src_addr;
uint16_t src_port;
pico_msginfo msginfo;
int r = 0;
while (true)
{
r = pico_socket_recvfrom_extended(s, buf, sizeof(buf), &src_addr.addr, &src_port, &msginfo);
if (r <= 0)
{
if (r < 0)
INFO_LOG(MODEM, "error UDP recv: %s", strerror(pico_err));
break;
}
sock_t sockfd = find_udp_socket(src_port);
if (VALID(sockfd))
{
sockaddr_in dst_addr;
socklen_t addr_len = sizeof(dst_addr);
memset(&dst_addr, 0, sizeof(dst_addr));
dst_addr.sin_family = AF_INET;
dst_addr.sin_addr.s_addr = msginfo.local_addr.ip4.addr;
dst_addr.sin_port = msginfo.local_port;
if (sendto(sockfd, buf, r, 0, (const sockaddr *)&dst_addr, addr_len) < 0)
perror("sendto udp socket");
}
}
}
if (ev & PICO_SOCK_EV_ERR) {
INFO_LOG(MODEM, "UDP Callback error received");
}
}
static void read_native_sockets()
{
int r;
sockaddr_in src_addr;
socklen_t addr_len;
// Accept incoming TCP connections
for (auto it = tcp_listening_sockets.begin(); it != tcp_listening_sockets.end(); it++)
{
addr_len = sizeof(src_addr);
memset(&src_addr, 0, addr_len);
sock_t sockfd = accept(it->second, (sockaddr *)&src_addr, &addr_len);
if (!VALID(sockfd))
{
if (get_last_error() != L_EAGAIN && get_last_error() != L_EWOULDBLOCK)
perror("accept");
continue;
}
//printf("Incoming TCP connection from %08x to port %d\n", src_addr.sin_addr.s_addr, short_be(it->first));
pico_socket *ps = pico_socket_open(PICO_PROTO_IPV4, PICO_PROTO_TCP, &tcp_callback);
if (ps == NULL)
{
INFO_LOG(MODEM, "pico_socket_open failed: error %d", pico_err);
closesocket(sockfd);
continue;
}
ps->local_addr.ip4.addr = src_addr.sin_addr.s_addr;
ps->local_port = src_addr.sin_port;
if (pico_socket_connect(ps, &dcaddr.addr, it->first) != 0)
{
INFO_LOG(MODEM, "pico_socket_connect failed: error %d", pico_err);
closesocket(sockfd);
pico_socket_close(ps);
continue;
}
set_non_blocking(sockfd);
set_tcp_nodelay(sockfd);
tcp_sockets.emplace(std::piecewise_construct,
std::forward_as_tuple(ps),
std::forward_as_tuple(ps, sockfd));
}
// Check connecting outbound TCP sockets
fd_set write_fds{};
fd_set error_fds{};
FD_ZERO(&write_fds);
FD_ZERO(&error_fds);
int max_fd = -1;
for (auto it = tcp_connecting_sockets.begin(); it != tcp_connecting_sockets.end(); it++)
{
FD_SET(it->second, &write_fds);
FD_SET(it->second, &error_fds);
max_fd = std::max(max_fd, (int)it->second);
}
if (max_fd > -1)
{
timeval tv{};
int rc = select(max_fd + 1, nullptr, &write_fds, &error_fds, &tv);
if (rc == -1)
perror("select");
else if (rc > 0)
{
for (auto it = tcp_connecting_sockets.begin(); it != tcp_connecting_sockets.end(); )
{
if (!FD_ISSET(it->second, &write_fds) && !FD_ISSET(it->second, &error_fds))
{
it++;
continue;
}
int error;
#ifdef _WIN32
char *value = (char *)&error;
#else
int *value = &error;
#endif
socklen_t l = sizeof(int);
if (getsockopt(it->second, SOL_SOCKET, SO_ERROR, value, &l) < 0 || error != 0)
{
char peer[30];
pico_ipv4_to_string(peer, it->first->local_addr.ip4.addr);
INFO_LOG(MODEM, "TCP connection to %s:%d failed: %s", peer, short_be(it->first->local_port), strerror(get_last_error()));
pico_socket_close(it->first);
closesocket(it->second);
}
else
{
set_tcp_nodelay(it->second);
tcp_sockets.emplace(std::piecewise_construct,
std::forward_as_tuple(it->first),
std::forward_as_tuple(it->first, it->second));
read_from_dc_socket(it->first, it->second);
}
it = tcp_connecting_sockets.erase(it);
}
}
}
static char buf[1500];
pico_msginfo msginfo;
// Read UDP sockets
for (auto it = udp_sockets.begin(); it != udp_sockets.end(); it++)
{
if (!VALID(it->second))
continue;
addr_len = sizeof(src_addr);
memset(&src_addr, 0, addr_len);
r = (int)recvfrom(it->second, buf, sizeof(buf), 0, (sockaddr *)&src_addr, &addr_len);
// filter out messages coming from ourselves (happens for broadcasts)
if (r > 0 && !is_local_address(src_addr.sin_addr.s_addr))
{
msginfo.dev = pico_dev;
msginfo.tos = 0;
msginfo.ttl = 0;
msginfo.local_addr.ip4.addr = src_addr.sin_addr.s_addr;
msginfo.local_port = src_addr.sin_port;
int r2 = pico_socket_sendto_extended(pico_udp_socket, buf, r, &dcaddr, it->first, &msginfo);
if (r2 < r)
INFO_LOG(MODEM, "error UDP sending to %d: %s", short_be(it->first), strerror(pico_err));
}
else if (r < 0 && get_last_error() != L_EAGAIN && get_last_error() != L_EWOULDBLOCK)
{
perror("recvfrom udp socket");
continue;
}
}
// Read TCP sockets
for (auto it = tcp_sockets.begin(); it != tcp_sockets.end(); )
{
it->second.receive_native();
if (it->second.pico_sock == nullptr)
it = tcp_sockets.erase(it);
else
it++;
}
}
static void close_native_sockets()
{
for (auto it = udp_sockets.begin(); it != udp_sockets.end(); it++)
closesocket(it->second);
udp_sockets.clear();
tcp_sockets.clear();
for (auto it = tcp_connecting_sockets.begin(); it != tcp_connecting_sockets.end(); it++)
{
pico_socket_close(it->first);
closesocket(it->second);
}
tcp_connecting_sockets.clear();
}
static int modem_set_speed(pico_device *dev, uint32_t speed)
{
return 0;
}
static void check_dns_entries()
{
static uint32_t dns_query_start = 0;
static uint32_t dns_query_attempts = 0;
if (afo_ip.addr == 0)
{
if (!dns_query_start)
{
dns_query_start = PICO_TIME_MS();
get_host_by_name("auriga.segasoft.com", dnsaddr); // Alien Front Online server
}
else
{
if (get_dns_answer(&afo_ip, dnsaddr) == 0)
{
dns_query_attempts = 0;
dns_query_start = 0;
char afoip[16];
pico_ipv4_to_string(afoip, afo_ip.addr);
INFO_LOG(MODEM, "AFO server IP is %s", afoip);
}
else
{
if (PICO_TIME_MS() - dns_query_start > 1000)
{
if (++dns_query_attempts >= 5)
{
u32 addr;
pico_string_to_ipv4("146.185.135.179", &addr); // Default address
memcpy(&afo_ip.addr, &addr, sizeof(addr));
dns_query_attempts = 0;
}
else
// Retry
dns_query_start = 0;
}
}
}
}
}
static pico_device *pico_eth_create()
{
pico_device *eth = (pico_device *)PICO_ZALLOC(sizeof(pico_device));
if (!eth)
return nullptr;
const u8 mac_addr[6] = { 0xc, 0xa, 0xf, 0xe, 0, 1 };
if (0 != pico_device_init(eth, "ETHPEER", mac_addr))
return nullptr;
DEBUG_LOG(NETWORK, "Device %s created", eth->name);
return eth;
}
//#define BBA_PCAPNG_DUMP
static FILE *pcapngDump;
static void dumpFrame(const u8 *frame, u32 size)
{
#ifdef BBA_PCAPNG_DUMP
if (pcapngDump == nullptr)
{
pcapngDump = fopen("bba.pcapng", "wb");
if (pcapngDump == nullptr)
{
const char *home = getenv("HOME");
if (home != nullptr)
{
std::string path = home + std::string("/bba.pcapng");
pcapngDump = fopen(path.c_str(), "wb");
}
if (pcapngDump == nullptr)
return;
}
u32 blockType = 0x0A0D0D0A; // Section Header Block
fwrite(&blockType, sizeof(blockType), 1, pcapngDump);
u32 blockLen = 28;
fwrite(&blockLen, sizeof(blockLen), 1, pcapngDump);
u32 magic = 0x1A2B3C4D;
fwrite(&magic, sizeof(magic), 1, pcapngDump);
u32 version = 1; // 1.0
fwrite(&version, sizeof(version), 1, pcapngDump);
u64 sectionLength = ~0; // unspecified
fwrite(&sectionLength, sizeof(sectionLength), 1, pcapngDump);
fwrite(&blockLen, sizeof(blockLen), 1, pcapngDump);
blockType = 1; // Interface Description Block
fwrite(&blockType, sizeof(blockType), 1, pcapngDump);
blockLen = 20;
fwrite(&blockLen, sizeof(blockLen), 1, pcapngDump);
const u32 linkType = 1; // Ethernet
fwrite(&linkType, sizeof(linkType), 1, pcapngDump);
const u32 snapLen = 0; // no limit
fwrite(&snapLen, sizeof(snapLen), 1, pcapngDump);
// TODO options? if name, ip/mac address
fwrite(&blockLen, sizeof(blockLen), 1, pcapngDump);
}
const u32 blockType = 6; // Extended Packet Block
fwrite(&blockType, sizeof(blockType), 1, pcapngDump);
u32 roundedSize = ((size + 3) & ~3) + 32;
fwrite(&roundedSize, sizeof(roundedSize), 1, pcapngDump);
u32 ifId = 0;
fwrite(&ifId, sizeof(ifId), 1, pcapngDump);
u64 now = (u64)(os_GetSeconds() * 1000000.0);
fwrite((u32 *)&now + 1, 4, 1, pcapngDump);
fwrite(&now, 4, 1, pcapngDump);
fwrite(&size, sizeof(size), 1, pcapngDump);
fwrite(&size, sizeof(size), 1, pcapngDump);
fwrite(frame, 1, size, pcapngDump);
fwrite(frame, 1, roundedSize - size - 32, pcapngDump);
fwrite(&roundedSize, sizeof(roundedSize), 1, pcapngDump);
#endif
}
static void closeDumpFile()
{
if (pcapngDump != nullptr)
{
fclose(pcapngDump);
pcapngDump = nullptr;
}
}
void pico_receive_eth_frame(const u8 *frame, u32 size)
{
dumpFrame(frame, size);
if (pico_dev != nullptr)
pico_stack_recv(pico_dev, (u8 *)frame, size);
}
static int send_eth_frame(pico_device *dev, void *data, int len)
{
dumpFrame((const u8 *)data, len);
return pico_send_eth_frame((const u8 *)data, len);
}
static void *pico_thread_func(void *)
{
if (!pico_stack_inited)
{
pico_stack_init();
pico_stack_inited = true;
#ifdef _WIN32
static WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 0), &wsaData) != 0)
WARN_LOG(MODEM, "WSAStartup failed");
#endif
}
// Find the network ports for the current game
const GamePortList *ports = nullptr;
std::string gameId;
if (settings.platform.isConsole())
{
gameId = std::string(ip_meta.product_number, sizeof(ip_meta.product_number));
gameId = trim_trailing_ws(gameId);
}
else
{
gameId = naomi_game_id;
}
for (u32 i = 0; i < ARRAY_SIZE(GamesPorts) && ports == nullptr; i++)
{
const auto& game = GamesPorts[i];
for (u32 j = 0; j < ARRAY_SIZE(game.gameId) && game.gameId[j] != nullptr; j++)
{
if (gameId == game.gameId[j])
{
NOTICE_LOG(MODEM, "Found network ports for game %s", gameId.c_str());
ports = &game;
break;
}
}
}
// Web TV requires the VJ compression option, which picotcp doesn't support.
// This hack allows WebTV to connect although the correct fix would
// be to implement VJ compression.
dont_reject_opt_vj_hack = gameId == "6107117" || gameId == "610-7390" || gameId == "610-7391" ? 1 : 0;
std::future<MiniUPnP> upnp =
std::async(std::launch::async, [ports]() {
// Initialize miniupnpc and map network ports
MiniUPnP upnp;
if (ports != nullptr && config::EnableUPnP)
{
if (!upnp.Init())
WARN_LOG(MODEM, "UPNP Init failed");
else
{
for (u32 i = 0; i < ARRAY_SIZE(ports->udpPorts) && ports->udpPorts[i] != 0; i++)
if (!upnp.AddPortMapping(ports->udpPorts[i], false))
WARN_LOG(MODEM, "UPNP AddPortMapping UDP %d failed", ports->udpPorts[i]);
for (u32 i = 0; i < ARRAY_SIZE(ports->tcpPorts) && ports->tcpPorts[i] != 0; i++)
if (!upnp.AddPortMapping(ports->tcpPorts[i], true))
WARN_LOG(MODEM, "UPNP AddPortMapping TCP %d failed", ports->tcpPorts[i]);
}
}
return upnp;
});
// Empty queues
{
std::queue<u8> empty;
in_buffer_lock.lock();
std::swap(in_buffer, empty);
in_buffer_lock.unlock();
std::queue<u8> empty2;
out_buffer_lock.lock();
std::swap(out_buffer, empty2);
out_buffer_lock.unlock();
}
u32 addr;
pico_string_to_ipv4(config::DNS.get().c_str(), &addr);
memcpy(&dnsaddr.addr, &addr, sizeof(addr));
// Create ppp/eth device
const bool usingPPP = !config::EmulateBBA;
if (usingPPP)
{
// PPP
pico_dev = pico_ppp_create();
if (!pico_dev)
return NULL;
pico_string_to_ipv4("192.168.167.2", &addr);
memcpy(&dcaddr.addr, &addr, sizeof(addr));
pico_ppp_set_peer_ip(pico_dev, dcaddr);
pico_string_to_ipv4("192.168.167.1", &addr);
pico_ip4 ipaddr;
memcpy(&ipaddr.addr, &addr, sizeof(addr));
pico_ppp_set_ip(pico_dev, ipaddr);
pico_ppp_set_dns1(pico_dev, dnsaddr);
pico_ppp_set_serial_read(pico_dev, modem_read);
pico_ppp_set_serial_write(pico_dev, modem_write);
pico_ppp_set_serial_set_speed(pico_dev, modem_set_speed);
pico_dev->proxied = 1;
pico_ppp_connect(pico_dev);
}
else
{
// Ethernet
pico_dev = pico_eth_create();
if (pico_dev == nullptr)
return nullptr;
pico_dev->send = &send_eth_frame;
pico_dev->proxied = 1;
pico_queue_protect(pico_dev->q_in);
pico_string_to_ipv4("192.168.169.1", &addr);
pico_ip4 ipaddr;
memcpy(&ipaddr.addr, &addr, sizeof(addr));
pico_string_to_ipv4("255.255.255.0", &addr);
pico_ip4 netmask;
memcpy(&netmask.addr, &addr, sizeof(addr));
pico_ipv4_link_add(pico_dev, ipaddr, netmask);
// dreamcast IP
pico_string_to_ipv4("192.168.169.2", &addr);
memcpy(&dcaddr.addr, &addr, sizeof(addr));
pico_dhcp_server_setting dhcpSettings{ 0 };
dhcpSettings.dev = pico_dev;
dhcpSettings.server_ip = ipaddr;
dhcpSettings.lease_time = long_be(24 * 60 * 60); // seconds
dhcpSettings.netmask = netmask;
dhcpSettings.pool_start = addr;
dhcpSettings.pool_end = addr;
dhcpSettings.pool_next = addr;
dhcpSettings.dns_server.addr = dnsaddr.addr;
if (pico_dhcp_server_initiate(&dhcpSettings) != 0)
WARN_LOG(MODEM, "DHCP server init failed");
}
pico_udp_socket = pico_socket_open(PICO_PROTO_IPV4, PICO_PROTO_UDP, &udp_callback);
if (pico_udp_socket == NULL) {
INFO_LOG(MODEM, "error opening UDP socket: %s", strerror(pico_err));
return nullptr;
}
int yes = 1;
pico_ip4 inaddr_any = {0};
uint16_t listen_port = 0;
int ret = pico_socket_bind(pico_udp_socket, &inaddr_any, &listen_port);
if (ret < 0)
INFO_LOG(MODEM, "error binding UDP socket to port %u: %s", short_be(listen_port), strerror(pico_err));
pico_tcp_socket = pico_socket_open(PICO_PROTO_IPV4, PICO_PROTO_TCP, &tcp_callback);
if (pico_tcp_socket == NULL) {
INFO_LOG(MODEM, "error opening TCP socket: %s", strerror(pico_err));
}
pico_socket_setoption(pico_tcp_socket, PICO_TCP_NODELAY, &yes);
ret = pico_socket_bind(pico_tcp_socket, &inaddr_any, &listen_port);
if (ret < 0) {
INFO_LOG(MODEM, "error binding TCP socket to port %u: %s", short_be(listen_port), strerror(pico_err));
}
else
{
if (pico_socket_listen(pico_tcp_socket, 10) != 0)
INFO_LOG(MODEM, "error listening on port %u", short_be(listen_port));
}
// Open listening sockets
sockaddr_in saddr;
socklen_t saddr_len = sizeof(saddr);
memset(&saddr, 0, sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = INADDR_ANY;
if (ports != nullptr)
{
for (u32 i = 0; i < ARRAY_SIZE(ports->udpPorts) && ports->udpPorts[i] != 0; i++)
{
uint16_t port = short_be(ports->udpPorts[i]);
sock_t sockfd = find_udp_socket(port);
saddr.sin_port = port;
if (::bind(sockfd, (sockaddr *)&saddr, saddr_len) < 0)
{
perror("bind");
closesocket(sockfd);
auto it = udp_sockets.find(port);
if (it != udp_sockets.end())
it->second = INVALID_SOCKET;
continue;
}
}
for (u32 i = 0; i < ARRAY_SIZE(ports->tcpPorts) && ports->tcpPorts[i] != 0; i++)
{
uint16_t port = short_be(ports->tcpPorts[i]);
saddr.sin_port = port;
sock_t sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (::bind(sockfd, (sockaddr *)&saddr, saddr_len) < 0)
{
perror("bind");
closesocket(sockfd);
continue;
}
if (listen(sockfd, 5) < 0)
{
perror("listen");
closesocket(sockfd);
continue;
}
set_non_blocking(sockfd);
tcp_listening_sockets[port] = sockfd;
}
}
while (pico_thread_running)
{
read_native_sockets();
pico_stack_tick();
check_dns_entries();
PICO_IDLE();
}
for (auto it = tcp_listening_sockets.begin(); it != tcp_listening_sockets.end(); it++)
closesocket(it->second);
close_native_sockets();
pico_socket_close(pico_tcp_socket);
pico_socket_close(pico_udp_socket);
if (pico_dev)
{
if (usingPPP)
{
pico_ppp_destroy(pico_dev);
}
else
{
closeDumpFile();
pico_dhcp_server_destroy(pico_dev);
pico_device_destroy(pico_dev);
}
pico_dev = nullptr;
}
pico_stack_tick();
if (ports != nullptr)
upnp.get().Term();
return NULL;
}
static cThread pico_thread(pico_thread_func, NULL);
bool start_pico()
{
emu.setNetworkState(true);
if (pico_thread_running)
return false;
pico_thread_running = true;
pico_thread.Start();
return true;
}
void stop_pico()
{
emu.setNetworkState(false);
pico_thread_running = false;
pico_thread.WaitToEnd();
}
// picotcp mutex implementation
extern "C" {
void *pico_mutex_init(void) {
return new std::mutex();
}
void pico_mutex_lock(void *mux) {
((std::mutex *)mux)->lock();
}
void pico_mutex_unlock(void *mux) {
((std::mutex *)mux)->unlock();
}
void pico_mutex_deinit(void *mux) {
delete (std::mutex *)mux;
}
}