flycast/core/hw/modem/picoppp.cpp

871 lines
21 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(_MSC_VER) && !defined(TARGET_NO_THREADS)
#include "stdclass.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 "network/net_platform.h"
#include "types.h"
#include "cfg/cfg.h"
#include "picoppp.h"
#include <map>
#include <mutex>
#include <queue>
#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 struct pico_device *ppp;
static std::queue<u8> in_buffer;
static std::queue<u8> out_buffer;
static std::mutex in_buffer_lock;
static std::mutex out_buffer_lock;
struct pico_ip4 dcaddr;
struct pico_ip4 dnsaddr;
static struct pico_socket *pico_tcp_socket, *pico_udp_socket;
struct pico_ip4 public_ip;
struct 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<u8> in_buffer;
void receive_native()
{
size_t len;
const u8 *data;
u8 buf[512];
if (!in_buffer.empty())
{
len = in_buffer.size();
data = &in_buffer[0];
}
else
{
if (native_sock == INVALID_SOCKET)
return;
int r = 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));
pico_socket_shutdown(pico_sock, PICO_SHUT_RDWR);
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;
pico_socket_shutdown(pico_sock, PICO_SHUT_RDWR);
}
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, 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<u8> remain(len);
memcpy(&remain[0], &data[r2], len);
std::swap(in_buffer, remain);
}
}
else
{
in_buffer.clear();
}
}
};
// tcp sockets
static std::map<struct pico_socket *, socket_pair> tcp_sockets;
static std::map<struct pico_socket *, sock_t> tcp_connecting_sockets;
// udp sockets: src port -> socket fd
static std::map<uint16_t, sock_t> udp_sockets;
static const uint16_t games_udp_ports[] = {
7980, // Alien Front Online
9789, // ChuChu Rocket
// NBA/NFL/NCAA 2K Series
5502,
5503,
5656,
3512, // The Next Tetris
6001, // Ooga Booga
// PBA Tour Bowling 2001, Starlancer
// 2300-2400, ?
6500,
13139,
// Planet Ring
7648,
1285,
1028,
// World Series Baseball 2K2
37171,
13713,
};
static const uint16_t games_tcp_ports[] = {
// NBA/NFL/NCAA 2K Series
5011,
6666,
3512, // The Next Tetris
// PBA Tour Bowling 2001, Starlancer
// 2300-2400, ?
47624,
17219, // Worms World Party
};
// 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;
static void read_native_sockets();
void get_host_by_name(const char *name, struct pico_ip4 dnsaddr);
int get_dns_answer(struct pico_ip4 *address, struct pico_ip4 dnsaddr);
static int modem_read(struct 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(struct 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;
usleep(5000);
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, struct 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)
{
struct pico_ip4 orig;
uint16_t port;
char peer[30];
int yes = 1;
struct 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);
/* 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
{
struct 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, (struct 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
// FIXME Need to clean up at some point?
udp_sockets[src_port] = sockfd;
return sockfd;
}
static void udp_callback(uint16_t ev, struct pico_socket *s)
{
if (ev & PICO_SOCK_EV_RD)
{
char buf[1510];
struct 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))
{
struct 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 struct 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;
struct 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, (struct 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));
struct 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_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);
}
struct 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);
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);
}
}
char buf[1500];
struct 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 = recvfrom(it->second, buf, sizeof(buf), 0, (struct sockaddr *)&src_addr, &addr_len);
if (r > 0)
{
msginfo.dev = ppp;
msginfo.tos = 0;
msginfo.ttl = 0;
msginfo.local_addr.ip4.addr = src_addr.sin_addr.s_addr;
msginfo.local_port = src_addr.sin_port;
//printf("read_native_sockets UDP received %d bytes from %08x:%d\n", 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(); )
{
it->second.receive_native();
if (it->second.pico_sock == nullptr)
it = tcp_sockets.erase(it);
else
it++;
}
}
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(struct 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 (public_ip.addr == 0)
{
if (!dns_query_start)
{
dns_query_start = PICO_TIME_MS();
struct pico_ip4 tmpdns;
pico_string_to_ipv4(RESOLVER1_OPENDNS_COM, &tmpdns.addr);
get_host_by_name("myip.opendns.com", tmpdns);
}
else
{
struct pico_ip4 tmpdns;
pico_string_to_ipv4(RESOLVER1_OPENDNS_COM, &tmpdns.addr);
if (get_dns_answer(&public_ip, tmpdns) == 0)
{
dns_query_attempts = 0;
dns_query_start = 0;
char myip[16];
pico_ipv4_to_string(myip, public_ip.addr);
INFO_LOG(MODEM, "My IP is %s", myip);
}
else
{
if (PICO_TIME_MS() - dns_query_start > 1000)
{
if (++dns_query_attempts >= 5)
{
public_ip.addr = 0xffffffff; // Bogus but not null
dns_query_attempts = 0;
}
else
// Retry
dns_query_start = 0;
}
}
}
}
else 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)
{
pico_string_to_ipv4("146.185.135.179", &afo_ip.addr); // Default address
dns_query_attempts = 0;
}
else
// Retry
dns_query_start = 0;
}
}
}
}
}
static void *pico_thread_func(void *)
{
struct pico_ip4 ipaddr;
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
}
// PPP
ppp = pico_ppp_create();
if (!ppp)
return NULL;
pico_string_to_ipv4("192.168.167.2", &dcaddr.addr);
pico_ppp_set_peer_ip(ppp, dcaddr);
pico_string_to_ipv4("192.168.167.1", &ipaddr.addr);
pico_ppp_set_ip(ppp, ipaddr);
pico_string_to_ipv4(settings.network.dns.c_str(), &dnsaddr.addr);
pico_ppp_set_dns1(ppp, dnsaddr);
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));
}
int yes = 1;
struct 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));
}
ppp->proxied = 1;
struct 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;
for (u32 i = 0; i < sizeof(games_udp_ports) / sizeof(uint16_t); i++)
{
uint16_t port = short_be(games_udp_ports[i]);
sock_t sockfd = find_udp_socket(port);
saddr.sin_port = port;
if (::bind(sockfd, (struct 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 < sizeof(games_tcp_ports) / sizeof(uint16_t); i++)
{
uint16_t port = short_be(games_tcp_ports[i]);
saddr.sin_port = port;
sock_t sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (::bind(sockfd, (struct 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;
}
{
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();
}
pico_ppp_set_serial_read(ppp, modem_read);
pico_ppp_set_serial_write(ppp, modem_write);
pico_ppp_set_serial_set_speed(ppp, modem_set_speed);
pico_ppp_connect(ppp);
while (pico_thread_running)
{
read_native_sockets();
pico_stack_tick();
check_dns_entries();
usleep(5000);
}
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 (ppp)
{
pico_ppp_destroy(ppp);
ppp = NULL;
}
pico_stack_tick();
return NULL;
}
static cThread pico_thread(pico_thread_func, NULL);
bool start_pico()
{
pico_thread_running = true;
pico_thread.Start();
return true;
}
void stop_pico()
{
pico_thread_running = false;
pico_thread.WaitToEnd();
}
#else
#include "types.h"
bool start_pico() { return false; }
void stop_pico() { }
void write_pico(u8 b) { }
int read_pico() { return -1; }
#endif