Files
LibretroAdmin c04b91fe69 network: modem V.42 and 56K, the Broadband Adapter, DCNet
The modem:
- V.42 error correction (LAPM) and V.42bis compression for the games
  that dial in sync mode, which were forced to async, and V.8bis and
  56K (V.90/K56 speeds).
- Receive and transmit FIFOs timed by the line's speeds, the TXFNF/TXHF
  status bits, and Power Smash's symmetric speed.
- PPP waits for the game to speak first, on the network thread; this
  replaces the quarter-second delay on reads.
- Its ID reports the country as USA, as upstream's.
- Frames short or long, an XID with bad lengths, a REJ for a frame that
  is gone, a V.8bis message without its end, a V.42bis step-up of a
  dictionary not a power of two: none can read or write past a buffer,
  loop for ever or be refused wrongly any more.

The Broadband Adapter could not be turned on: settings forced it off
and nothing set it. The option "Expansion Port Device (Restart
Required)" (reicast_network_device) chooses the modem, the adapter or
nothing; the modem by default, as before. The adapter gets its
link-change interrupt 10 ms after a reset, as KallistiOS waits for, frames
under 6 or over 1514 bytes are dropped, and the network starts with the
game's first frame and stops on a GAPS reset.

DCNet: "Use DCNet" (reicast_dcnet, off by default) goes online through
the dcnet.flyca.st relay instead of picoTCP, on the same network thread,
non-blocking, without locks. Power Smash and Pro Yakyuu always go
through it, as upstream has them.

picoTCP: dns.flyca.st as the name server; a socket that is ready is read
whole (an upload no longer stalls); the ports of Daytona, Sega Tetris,
Golf Shiyouyo 2, Dee Dee Planet, Driving Strikers, Floigan and the
Internet Game Pack. Stopping the network thread no longer waits for it.

In the save state (V24), on a Dreamcast: the device chosen and what the
modem (FIFOs, V.42, V.42bis, V.8bis) and the adapter (GAPS, its RAM, the
RTL8139) hold, 34 KB. A live connection cannot be in a state: one loaded
replays what came over the line at the times it came, and a line the
state does not have hangs up, without waiting for the thread.

tools/threads/pico_test.cpp tests the thread with the new interface,
and DCNet against a stand-in relay.
2026-10-13 14:37:55 +02:00

1603 lines
44 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/>.
*/
#if defined(_WIN32) && !defined(FD_SETSIZE)
/* The pico thread waits on all of its sockets at once; Winsock's own
* limit for a select() is 64. */
#define FD_SETSIZE 256
#endif
#if !defined(_MSC_VER) && !defined(TARGET_NO_THREADS)
#include "stdclass.h"
#include "hw/sh4/sh4_sched.h"
#ifdef __MINGW32__
#define _POSIX_SOURCE
#endif
#ifdef __LIBRETRO__
#include <retro_timers.h>
#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 "network/net_platform.h"
#include "types.h"
#include "picoppp.h"
#include "netservice.h"
#include "netthread.h"
#include "network/miniupnp.h"
#include "reios/reios.h"
#include <map>
#include <retro_spsc.h>
#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;
/* Bytes between the emulated modem (emulation thread) and the PPP stack
* (pico thread). Each ring has one writer and one reader and takes no
* lock. in_ring is kept small on purpose: when it fills, the stack waits
* for the modem to read, which is the flow control the link needs. */
#define IN_RING_BYTES 1024
#define OUT_RING_BYTES 16384
static retro_spsc_t in_ring; /* stack -> modem */
static retro_spsc_t out_ring; /* modem -> stack */
static bool rings_ready; /* emulation thread only */
/* Where the stack sleeps while in_ring is full. */
static cEventCount in_ring_ec;
/* Set by a network thread that does not sleep on in_ring_ec (DCNet) when
* in_ring had no room: the modem wakes it once it has read some. */
static retro_atomic_int_t in_ring_blocked;
/* Ethernet frames, for the broadband adapter. The adapter is emulated on
* the emulation thread and the stack runs on the pico thread, and neither
* may call into the other: the adapter's registers, its receive ring in
* guest memory and its interrupt belong to one, the stack's queues to the
* other. So a frame crosses as bytes in a ring, a 16-bit length and then
* the frame, written whole or not at all. */
#define ETH_RING_BYTES 65536
#define ETH_FRAME_MAX NET_ETH_FRAME_MAX
static retro_spsc_t eth_out_ring; /* adapter -> stack */
static retro_spsc_t eth_in_ring; /* stack -> adapter */
/* Set by the pico thread when the adapter's ring had no room for a frame;
* the emulation thread wakes it once it has taken some out. */
static retro_atomic_int_t eth_in_blocked;
/* What the thread is for, set before it starts: the broadband adapter
* rather than the modem, DCNet rather than picoTCP. */
static bool thread_bba;
static bool thread_dcnet;
/* PPP: the stack says nothing until the Dreamcast has sent something. What
* it sends before is dropped: Windows CE games, and a V.42 negotiation,
* would take it for line noise. (pico thread) */
static bool ppp_passive;
/* The pico thread does not tick on a timer. It waits, in select(), for the
* things that give it work: its native sockets, the stack's next timer,
* and this socket, which the emulation thread writes a byte to when it has
* handed the stack something (modem bytes, an Ethernet frame) or wants the
* thread to stop. It is a datagram socket on the loopback interface
* connected to itself.
*
* So that the emulation thread does not make a system call for every byte
* the modem sends, it only writes when the pico thread says it is waiting.
* pico_kicks counts what has been handed over: the pico thread notes it
* before it looks for work and, having said it is about to wait, does not
* wait if the count has moved since. */
static sock_t wake_sock = INVALID_SOCKET; /* made by the emulation thread before the pico thread starts */
static retro_atomic_int_t pico_waiting;
static retro_atomic_int_t pico_kicks;
static void wake_pico()
{
retro_atomic_fetch_add_int(&pico_kicks, 1);
if (retro_atomic_load_acquire_int(&pico_waiting))
send(wake_sock, "", 1, L_MSG_NOSIGNAL);
}
static pico_ip4 dcaddr;
static pico_ip4 dnsaddr;
static pico_socket *pico_tcp_socket, *pico_udp_socket;
static pico_ip4 afo_ip;
/* The most a native socket may be owed before the connection is given up. */
#define OUT_BUFFER_MAX (1024 * 1024)
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;
std::vector<char> out_buffer; // from the Dreamcast, not yet taken by the native socket
bool shut_wr = false; // the Dreamcast has closed its side: passed on once out_buffer has gone
bool reading = false; // read_from_dc_socket is taking what the Dreamcast sent: a close waits for it
/* Sends the native socket what it has not taken yet and then @len bytes
* more. What it does not take now is kept, and the thread is woken when
* the socket has room for it (see wait_for_work). False if the
* connection is lost, or more is waiting than a live peer would leave. */
bool send_native(const char *data, size_t len)
{
const bool kept = !out_buffer.empty();
if (kept)
{
out_buffer.insert(out_buffer.end(), data, data + len);
data = &out_buffer[0];
len = out_buffer.size();
}
int r = (int)send(native_sock, data, len, L_MSG_NOSIGNAL);
if (r < 0)
{
if (get_last_error() != L_EAGAIN && get_last_error() != L_EWOULDBLOCK)
return false;
r = 0;
}
if (kept)
out_buffer.erase(out_buffer.begin(), out_buffer.begin() + r);
else
out_buffer.assign(data + r, data + len);
pass_close();
return out_buffer.size() <= OUT_BUFFER_MAX;
}
/* Passes the Dreamcast's close on, once nothing it sent before closing
* is left to read or to send. */
void pass_close()
{
if (shut_wr && !reading && out_buffer.empty())
{
shut_wr = false;
if (native_sock != INVALID_SOCKET)
::shutdown(native_sock, SHUT_WR);
}
}
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;
}
// What is waiting in in_buffer went through here when it was received
if (data == buf && 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 },
{ },
},
{
{
"MK-51037", "HDR-0106", // Daytona USA
"HDR-0073", // Sega Tetris
"GENERIC", "T44501M" // Golf Shiyouyo 2
// (the dreamcastlive patched versions are id'ed as GENERIC)
},
{ 12079, 20675 },
{ },
},
{ // Dee Dee Planet
{ "HDR-0041" },
{ 9879 },
{ },
},
{ // Driving Strikers online demo
{ "IND-161053" },
{ 30099 },
{ },
},
{ // Floigan Bros
{ "MK-51114" },
{ },
{ 37001 },
},
{ // Internet Game Pack
{ "MK-51138" },
{ 5656 },
{ 5011, 10500, 10501, 10502, 10503 },
},
{ // 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 },
},
};
// listening port -> socket fd
static std::map<uint16_t, sock_t> tcp_listening_sockets;
static bool pico_stack_inited;
static retro_atomic_int_t pico_thread_running;
extern "C"
{
extern 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);
sock_t get_dns_socket(void);
static int modem_read(pico_device *dev, void *data, int len)
{
int r = (int)retro_spsc_read(&out_ring, data, len);
if (r > 0)
ppp_passive = false;
return r;
}
static int modem_write(pico_device *dev, const void *data, int len)
{
const u8 *p = (const u8 *)data;
int left = len;
if (ppp_passive)
return len;
while (left > 0)
{
int key;
size_t n = retro_spsc_write(&in_ring, p, left);
p += n;
left -= (int)n;
if (left == 0)
break;
/* Full: sleep until the modem has read some of it. */
key = in_ring_ec.Prepare();
if (!retro_atomic_load_acquire_int(&pico_thread_running))
{
in_ring_ec.Cancel();
return 0;
}
if (retro_spsc_write_avail(&in_ring) > 0)
in_ring_ec.Cancel();
else
in_ring_ec.Commit(key, 5000);
}
return len;
}
void write_pico(u8 b)
{
if (rings_ready)
{
retro_spsc_write(&out_ring, &b, 1);
wake_pico();
}
}
int read_pico()
{
u8 b;
if (!rings_ready || retro_spsc_read(&in_ring, &b, 1) != 1)
return -1;
in_ring_ec.Notify();
if (retro_atomic_load_acquire_int(&in_ring_blocked))
{
retro_atomic_store_release_int(&in_ring_blocked, 0);
wake_pico();
}
return b;
}
static void read_from_dc_socket(socket_pair& pair)
{
char buf[1510];
int r;
/* When the Dreamcast closes with bytes still unread, the stack says so
* from inside the read that comes next, whatever that read leaves
* behind. The close is not passed on from there (see tcp_callback): it
* would go out ahead of the bytes this read returns, and of the ones
* after them. So once a close is owed, all there is gets read here. */
pair.reading = true;
do
{
r = pico_socket_read(pair.pico_sock, buf, sizeof(buf));
if (r > 0 && !pair.send_native(buf, r))
{
pico_socket *pico_sock = pair.pico_sock;
perror("tcp_callback send");
tcp_sockets.erase(pico_sock);
return;
}
/* All there is: a read event can stand for more than one read's
* worth, and no other comes until more arrives (what the native
* socket does not take is kept: see send_native()) */
} while (r > 0);
pair.reading = false;
pair.pass_close();
}
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->second);
}
}
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 = selectable_socket(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);
set_no_sigpipe(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
{
// Not before the bytes still waiting for the native socket have
// gone, nor those of a read this comes from the middle of
if (!it->second.out_buffer.empty() || it->second.reading)
it->second.shut_wr = true;
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 = selectable_socket(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
// The port the Dreamcast sends from, when it can be had: a server may
// answer to the port the game is known to use and not to the one the
// datagram came from (Toy Racer). Otherwise any port the system has.
sockaddr_in saddr;
memset(&saddr, 0, sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = INADDR_ANY;
saddr.sin_port = src_port;
if (::bind(sockfd, (sockaddr *)&saddr, sizeof(saddr)) < 0)
{
INFO_LOG(MODEM, "UDP port %d cannot be had: using another", short_be(src_port));
saddr.sin_port = 0;
if (::bind(sockfd, (sockaddr *)&saddr, sizeof(saddr)) < 0)
perror("bind");
}
// 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;
}
DEBUG_LOG(MODEM, "udp_callback(read) recvd port %d: %d bytes", short_be(src_port), r);
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, L_MSG_NOSIGNAL, (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 = selectable_socket(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);
set_no_sigpipe(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_ZERO(&write_fds);
int max_fd = -1;
for (auto it = tcp_connecting_sockets.begin(); it != tcp_connecting_sockets.end(); it++)
{
FD_SET(it->second, &write_fds);
max_fd = std::max(max_fd, (int)it->second);
}
timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 0;
if (select(max_fd + 1, NULL, &write_fds, NULL, &tv) > 0)
{
for (auto it = tcp_connecting_sockets.begin(); it != tcp_connecting_sockets.end(); )
{
if (!FD_ISSET(it->second, &write_fds))
{
it++;
continue;
}
#ifdef _WIN32
char value;
#else
int value;
#endif
socklen_t l = sizeof(int);
if (getsockopt(it->second, SOL_SOCKET, SO_ERROR, &value, &l) < 0 || value)
{
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);
auto added = tcp_sockets.emplace(std::piecewise_construct,
std::forward_as_tuple(it->first),
std::forward_as_tuple(it->first, it->second));
read_from_dc_socket(added.first->second);
}
it = tcp_connecting_sockets.erase(it);
}
}
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);
if (r > 0)
{
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;
DEBUG_LOG(MODEM, "read_native_sockets UDP received %d bytes from %08x:%d", r, long_be(msginfo.local_addr.ip4.addr), short_be(msginfo.local_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(); )
{
if (!it->second.out_buffer.empty() && VALID(it->second.native_sock)
&& !it->second.send_native(nullptr, 0))
{
perror("tcp socket send");
it = tcp_sockets.erase(it);
continue;
}
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;
}
// A capture of every frame of the adapter, for debugging: never in a release
//#define BBA_PCAPNG_DUMP
static FILE *pcapngDump;
static void dumpFrame(const u8 *frame, u32 size)
{
#ifdef BBA_PCAPNG_DUMP
if (pcapngDump == nullptr)
{
const char *home = getenv("HOME");
if (home == nullptr)
return;
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 = 3; // Simple Packet Block
fwrite(&blockType, sizeof(blockType), 1, pcapngDump);
u32 roundedSize = ((size + 3) & ~3) + 16;
fwrite(&roundedSize, sizeof(roundedSize), 1, pcapngDump);
fwrite(&size, sizeof(size), 1, pcapngDump);
fwrite(frame, 1, size, pcapngDump);
fwrite(frame, 1, roundedSize - size - 16, pcapngDump);
fwrite(&roundedSize, sizeof(roundedSize), 1, pcapngDump);
#endif
}
static void closeDumpFile()
{
if (pcapngDump != nullptr)
{
fclose(pcapngDump);
pcapngDump = nullptr;
}
}
/* Emulation thread: the adapter has sent a frame. */
void pico_receive_eth_frame(const u8 *frame, u32 size)
{
u8 buf[2 + ETH_FRAME_MAX];
if (!rings_ready || size == 0 || size > ETH_FRAME_MAX)
return;
// No room: dropped, as a switch with a full queue would
if (retro_spsc_write_avail(&eth_out_ring) < size + 2)
return;
buf[0] = (u8)size;
buf[1] = (u8)(size >> 8);
memcpy(&buf[2], frame, size);
retro_spsc_write(&eth_out_ring, buf, size + 2);
wake_pico();
}
/* Pico thread: give the stack the frames the adapter has sent. */
/* No more than this many in one go. A guest that sends without stopping
* refills the ring as fast as it is emptied: taken until the ring was
* empty, its frames kept this thread here for good, and the stack's
* timers and the sockets - which come after - never had their turn (an
* ARP request in such a flood got its first answer and no other). */
#define ETH_FRAMES_A_PASS 256
/* True if there may be more waiting: the caller comes back for them once
* the rest has had its turn, without sleeping first. */
static bool take_eth_frames()
{
u8 buf[ETH_FRAME_MAX];
u8 len[2];
for (int taken = 0; taken < ETH_FRAMES_A_PASS; taken++)
{
if (retro_spsc_read(&eth_out_ring, len, 2) != 2)
return false;
const u32 size = len[0] | (len[1] << 8);
if (size > ETH_FRAME_MAX || retro_spsc_read(&eth_out_ring, buf, size) != size)
return false;
dumpFrame(buf, size);
if (pico_dev != nullptr)
pico_stack_recv(pico_dev, buf, size);
}
return true;
}
/* Pico thread: the stack has a frame for the adapter. */
static int send_eth_frame(pico_device *dev, void *data, int len)
{
u8 buf[2 + ETH_FRAME_MAX];
if (len <= 0 || len > ETH_FRAME_MAX)
return len; // nothing the adapter could take: let the stack drop it
if (retro_spsc_write_avail(&eth_in_ring) < (size_t)len + 2)
{
// The stack keeps the frame and tries again when it is next woken
retro_atomic_store_release_int(&eth_in_blocked, 1);
if (retro_spsc_write_avail(&eth_in_ring) < (size_t)len + 2)
return 0;
}
dumpFrame((const u8 *)data, len);
buf[0] = (u8)len;
buf[1] = (u8)(len >> 8);
memcpy(&buf[2], data, len);
retro_spsc_write(&eth_in_ring, buf, len + 2);
return len;
}
/* Emulation thread: the next frame the stack has sent the adapter, into
* @frame (ETH_FRAME_MAX bytes). Its size, 0 if there is none. */
u32 net_eth_take(u8 *frame)
{
u8 len[2];
if (!rings_ready || retro_spsc_read(&eth_in_ring, len, 2) != 2)
return 0;
u32 size = len[0] | (len[1] << 8);
// (frames are written whole: this cannot be short)
if (size > ETH_FRAME_MAX || retro_spsc_read(&eth_in_ring, frame, size) != size)
size = 0;
if (retro_atomic_load_acquire_int(&eth_in_blocked))
{
retro_atomic_store_release_int(&eth_in_blocked, 0);
wake_pico();
}
return size;
}
/* Waits until the thread has something to do: a native socket to read,
* accept on or finish connecting, a timer of the stack's come due,
* something handed over by the emulation thread, or the order to stop.
* @kicks is pico_kicks as it was before the thread last looked for work. */
static void wait_for_work(int kicks)
{
fd_set readable, writable;
timeval tv;
char drain[64];
int max_fd = (int)wake_sock;
int idle = pico_stack_idle_ms();
// The last tick moved a frame: there may be more
if (idle == 0)
return;
// Never longer than a second, whatever the stack's timers say
if (idle < 0 || idle > 1000)
idle = 1000;
FD_ZERO(&readable);
FD_ZERO(&writable);
FD_SET(wake_sock, &readable);
for (auto& it : udp_sockets)
if (VALID(it.second))
{
FD_SET(it.second, &readable);
max_fd = std::max(max_fd, (int)it.second);
}
for (auto& it : tcp_listening_sockets)
{
FD_SET(it.second, &readable);
max_fd = std::max(max_fd, (int)it.second);
}
for (auto& it : tcp_sockets)
{
// One with bytes the stack has not taken yet is not read until it has
if (VALID(it.second.native_sock) && it.second.in_buffer.empty())
{
FD_SET(it.second.native_sock, &readable);
max_fd = std::max(max_fd, (int)it.second.native_sock);
}
// One that is owed bytes: woken when it has room for them
if (VALID(it.second.native_sock) && !it.second.out_buffer.empty())
{
FD_SET(it.second.native_sock, &writable);
max_fd = std::max(max_fd, (int)it.second.native_sock);
}
}
for (auto& it : tcp_connecting_sockets)
{
FD_SET(it.second, &writable);
max_fd = std::max(max_fd, (int)it.second);
}
if (afo_ip.addr == 0 && VALID(get_dns_socket()))
{
FD_SET(get_dns_socket(), &readable);
max_fd = std::max(max_fd, (int)get_dns_socket());
}
tv.tv_sec = idle / 1000;
tv.tv_usec = (idle % 1000) * 1000;
retro_atomic_fetch_add_int(&pico_waiting, 1);
if (retro_atomic_load_acquire_int(&pico_thread_running)
&& retro_atomic_load_acquire_int(&pico_kicks) == kicks)
select(max_fd + 1, &readable, &writable, NULL, &tv);
retro_atomic_store_release_int(&pico_waiting, 0);
while (recv(wake_sock, drain, sizeof(drain), 0) > 0)
;
}
/* For DCNet (dcnet.cpp), which runs on this thread in place of the stack:
* the same rings, the same way of waiting. */
bool netthread_running()
{
return retro_atomic_load_acquire_int(&pico_thread_running) != 0;
}
int netthread_kicks()
{
return retro_atomic_load_acquire_int(&pico_kicks);
}
size_t netthread_modem_take(u8 *buf, size_t len)
{
return retro_spsc_read(&out_ring, buf, len);
}
size_t netthread_modem_give(const u8 *buf, size_t len)
{
size_t n = retro_spsc_write(&in_ring, buf, len);
if (n < len)
{
// Full: the modem wakes the thread when it has read some (read_pico())
retro_atomic_store_release_int(&in_ring_blocked, 1);
n += retro_spsc_write(&in_ring, buf + n, len - n);
}
return n;
}
u32 netthread_eth_take(u8 *frame)
{
u8 len[2];
if (retro_spsc_read(&eth_out_ring, len, 2) != 2)
return 0;
const u32 size = len[0] | (len[1] << 8);
if (size > ETH_FRAME_MAX || retro_spsc_read(&eth_out_ring, frame, size) != size)
return 0;
return size;
}
bool netthread_eth_give(const u8 *frame, u32 size)
{
return send_eth_frame(nullptr, (void *)frame, (int)size) != 0;
}
void netthread_wait(int kicks, fd_set *readable, fd_set *writable, int max_fd, int timeout_ms)
{
timeval tv;
char drain[64];
FD_SET(wake_sock, readable);
max_fd = std::max(max_fd, (int)wake_sock);
tv.tv_sec = timeout_ms / 1000;
tv.tv_usec = (timeout_ms % 1000) * 1000;
retro_atomic_fetch_add_int(&pico_waiting, 1);
if (retro_atomic_load_acquire_int(&pico_thread_running)
&& retro_atomic_load_acquire_int(&pico_kicks) == kicks)
select(max_fd + 1, readable, writable, NULL, &tv);
retro_atomic_store_release_int(&pico_waiting, 0);
while (recv(wake_sock, drain, sizeof(drain), 0) > 0)
;
}
/* The IPv4 address of @host, 0 if it has none. (This can take seconds: the
* emulation thread never waits for the network thread but at its next
* start, see stop_pico().) */
u32 netthread_resolve(const char *host)
{
u32 ip = 0;
addrinfo hints;
addrinfo *res = nullptr;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET;
if (getaddrinfo(host, nullptr, &hints, &res) == 0 && res != nullptr)
{
for (addrinfo *ai = res; ai != nullptr; ai = ai->ai_next)
if (ai->ai_family == AF_INET && ai->ai_addrlen >= sizeof(sockaddr_in))
{
ip = ((const sockaddr_in *)ai->ai_addr)->sin_addr.s_addr;
break;
}
freeaddrinfo(res);
}
return ip;
}
/* The Dreamcast's DNS server: the one asked for, the current default for
* the old one */
static void find_dns_server()
{
std::string name = settings.network.dns;
if (name == "46.101.91.123")
name = "dns.flyca.st";
u32 ip = 0;
if (pico_string_to_ipv4(name.c_str(), &ip) != 0 || ip == 0)
ip = netthread_resolve(name.c_str());
if (ip != 0)
{
char s[16];
memcpy(&dnsaddr.addr, &ip, sizeof(ip));
pico_ipv4_to_string(s, dnsaddr.addr);
NOTICE_LOG(MODEM, "%s IP is %s", name.c_str(), s);
}
else
{
pico_string_to_ipv4("46.101.91.123", &ip);
memcpy(&dnsaddr.addr, &ip, sizeof(ip));
WARN_LOG(MODEM, "Can't resolve %s. Using 46.101.91.123", name.c_str());
}
}
static void *pico_thread_func(void *)
{
if (thread_dcnet)
{
dcnet_run(thread_bba);
return NULL;
}
const bool bba = thread_bba;
// (the PPP stack's first words are to wait for the Dreamcast's)
ppp_passive = !bba;
if (!pico_stack_inited)
{
pico_stack_init();
pico_stack_inited = true;
#if _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(ip_meta.product_number, sizeof(ip_meta.product_number));
gameId = trim_trailing_ws(gameId);
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" ? 1 : 0;
// Initialize miniupnpc and map network ports
MiniUPnP upnp;
bool upnp_mapped = false;
if (ports != nullptr)
{
if (!upnp.Init())
WARN_LOG(MODEM, "UPNP Init failed");
else
{
upnp_mapped = true;
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]);
}
}
u32 addr;
find_dns_server();
// Create ppp/eth device
if (!bba)
{
// 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_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++)
{
// Bound to the port by find_udp_socket
find_udp_socket(short_be(ports->udpPorts[i]));
}
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 = selectable_socket(socket(AF_INET, SOCK_STREAM, IPPROTO_TCP));
if (!VALID(sockfd))
{
perror("socket");
continue;
}
set_reuse_addr(sockfd);
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 (retro_atomic_load_acquire_int(&pico_thread_running))
{
const int kicks = retro_atomic_load_acquire_int(&pico_kicks);
const bool more_frames = take_eth_frames();
read_native_sockets();
pico_stack_tick();
check_dns_entries();
if (!more_frames)
wait_for_work(kicks);
}
for (auto it = tcp_listening_sockets.begin(); it != tcp_listening_sockets.end(); it++)
closesocket(it->second);
// The next connection must not wait on, or accept from, these numbers
tcp_listening_sockets.clear();
close_native_sockets();
pico_socket_close(pico_tcp_socket);
pico_socket_close(pico_udp_socket);
if (pico_dev)
{
if (!bba)
{
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 (upnp_mapped)
upnp.Term();
return NULL;
}
static cThread pico_thread(pico_thread_func, NULL);
static bool make_wake_socket()
{
sockaddr_in addr;
socklen_t len = sizeof(addr);
if (VALID(wake_sock))
return true;
#ifdef _WIN32
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 0), &wsaData) != 0)
return false;
#endif
sock_t sock = selectable_socket(socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP));
if (!VALID(sock))
return false;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
if (::bind(sock, (sockaddr *)&addr, sizeof(addr)) < 0
|| getsockname(sock, (sockaddr *)&addr, &len) < 0
|| ::connect(sock, (sockaddr *)&addr, sizeof(addr)) < 0)
{
closesocket(sock);
return false;
}
set_non_blocking(sock);
wake_sock = sock;
return true;
}
bool start_pico()
{
if (retro_atomic_load_relaxed_int(&pico_thread_running))
return false;
// The last one, if it has not ended yet, is about to
pico_thread.WaitToEnd();
/* The pico thread is not running, so both rings are this thread's
* alone: make them, or empty them of the last connection's bytes. */
if (!rings_ready)
{
if (!retro_spsc_init(&in_ring, IN_RING_BYTES))
return false;
if (!retro_spsc_init(&out_ring, OUT_RING_BYTES))
{
retro_spsc_free(&in_ring);
return false;
}
if (!retro_spsc_init(&eth_out_ring, ETH_RING_BYTES))
{
retro_spsc_free(&in_ring);
retro_spsc_free(&out_ring);
return false;
}
if (!retro_spsc_init(&eth_in_ring, ETH_RING_BYTES))
{
retro_spsc_free(&in_ring);
retro_spsc_free(&out_ring);
retro_spsc_free(&eth_out_ring);
return false;
}
rings_ready = true;
}
else
{
retro_spsc_clear(&in_ring);
retro_spsc_clear(&out_ring);
retro_spsc_clear(&eth_out_ring);
retro_spsc_clear(&eth_in_ring);
}
retro_atomic_store_release_int(&eth_in_blocked, 0);
retro_atomic_store_release_int(&in_ring_blocked, 0);
if (!make_wake_socket())
return false;
thread_bba = net_device == NET_DEVICE_BBA;
{
std::string gameId(ip_meta.product_number, sizeof(ip_meta.product_number));
gameId = trim_trailing_ws(gameId);
thread_dcnet = net_dcnet_option
// these games' servers are reached through DCNet only
|| gameId == "HDR-0113" // Power Smash
|| gameId == "HDR-0091"; // Pro Yakyuu Team de Asobou Net!
if (thread_dcnet)
NOTICE_LOG(MODEM, "Network through DCNet");
}
retro_atomic_store_release_int(&pico_waiting, 0);
retro_atomic_store_release_int(&pico_thread_running, 1);
pico_thread.Start();
return true;
}
/* The thread is told to stop and is not waited for: it may be looking up a
* name or talking to the router (UPnP), which can take seconds. It is waited
* for before the next one starts, and when the machine stops. */
void stop_pico()
{
retro_atomic_store_release_int(&pico_thread_running, 0);
in_ring_ec.Notify();
if (VALID(wake_sock))
send(wake_sock, "", 1, L_MSG_NOSIGNAL);
}
void net_thread_join()
{
stop_pico();
pico_thread.WaitToEnd();
}
#else
#include "types.h"
bool start_pico() { return false; }
void stop_pico() { }
void net_thread_join() { }
void write_pico(u8 b) { }
int read_pico() { return -1; }
u32 net_eth_take(u8 *frame) { return 0; }
void pico_receive_eth_frame(const u8 *frame, u32 size) { }
#endif