2018-09-24 17:06:14 +00:00
|
|
|
/*
|
|
|
|
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/>.
|
|
|
|
*/
|
|
|
|
|
2019-02-06 17:42:45 +00:00
|
|
|
#if !defined(_MSC_VER) && !defined(TARGET_NO_THREADS)
|
|
|
|
|
2020-03-29 15:32:53 +00:00
|
|
|
#include "stdclass.h"
|
|
|
|
|
2018-10-30 10:29:30 +00:00
|
|
|
#ifdef __MINGW32__
|
2018-10-29 21:59:46 +00:00
|
|
|
#define _POSIX_SOURCE
|
2018-10-30 10:29:30 +00:00
|
|
|
#endif
|
|
|
|
|
2018-09-15 19:41:54 +00:00
|
|
|
extern "C" {
|
|
|
|
#include <pico_stack.h>
|
|
|
|
#include <pico_dev_ppp.h>
|
2018-09-24 17:06:14 +00:00
|
|
|
#include <pico_socket.h>
|
|
|
|
#include <pico_socket_tcp.h>
|
|
|
|
#include <pico_ipv4.h>
|
|
|
|
#include <pico_tcp.h>
|
2018-09-15 19:41:54 +00:00
|
|
|
}
|
|
|
|
|
2020-04-14 15:43:11 +00:00
|
|
|
#include "network/net_platform.h"
|
2018-09-15 19:41:54 +00:00
|
|
|
|
|
|
|
#include "types.h"
|
|
|
|
#include "cfg/cfg.h"
|
|
|
|
#include "picoppp.h"
|
|
|
|
|
2020-03-30 12:24:33 +00:00
|
|
|
#include <map>
|
|
|
|
#include <mutex>
|
|
|
|
#include <queue>
|
|
|
|
|
2018-09-24 17:06:14 +00:00
|
|
|
#define RESOLVER1_OPENDNS_COM "208.67.222.222"
|
|
|
|
#define AFO_ORIG_IP 0x83f2fb3f // 63.251.242.131 in network order
|
2019-08-14 07:17:07 +00:00
|
|
|
#define IGP_ORIG_IP 0xef2bd2cc // 204.210.43.239 in network order
|
2018-09-24 17:06:14 +00:00
|
|
|
|
2018-09-15 19:41:54 +00:00
|
|
|
static struct pico_device *ppp;
|
|
|
|
|
|
|
|
static std::queue<u8> in_buffer;
|
|
|
|
static std::queue<u8> out_buffer;
|
|
|
|
|
2020-03-30 12:24:33 +00:00
|
|
|
static std::mutex in_buffer_lock;
|
|
|
|
static std::mutex out_buffer_lock;
|
2018-09-24 17:06:14 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
// src socket -> socket fd
|
2020-03-29 17:29:14 +00:00
|
|
|
static std::map<struct pico_socket *, sock_t> tcp_sockets;
|
|
|
|
static std::map<struct pico_socket *, sock_t> tcp_connecting_sockets;
|
2018-09-24 17:06:14 +00:00
|
|
|
// src port -> socket fd
|
2020-03-29 17:29:14 +00:00
|
|
|
static std::map<uint16_t, sock_t> udp_sockets;
|
2018-09-24 17:06:14 +00:00
|
|
|
|
|
|
|
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
|
2020-03-29 17:29:14 +00:00
|
|
|
static std::map<uint16_t, sock_t> tcp_listening_sockets;
|
2018-09-24 17:06:14 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
|
2018-09-15 19:41:54 +00:00
|
|
|
static int modem_read(struct pico_device *dev, void *data, int len)
|
|
|
|
{
|
|
|
|
u8 *p = (u8 *)data;
|
|
|
|
|
|
|
|
int count = 0;
|
2020-03-30 12:24:33 +00:00
|
|
|
out_buffer_lock.lock();
|
2018-09-15 19:41:54 +00:00
|
|
|
while (!out_buffer.empty() && count < len)
|
|
|
|
{
|
|
|
|
*p++ = out_buffer.front();
|
|
|
|
out_buffer.pop();
|
|
|
|
count++;
|
|
|
|
}
|
2020-03-30 12:24:33 +00:00
|
|
|
out_buffer_lock.unlock();
|
2018-09-15 19:41:54 +00:00
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int modem_write(struct pico_device *dev, const void *data, int len)
|
|
|
|
{
|
|
|
|
u8 *p = (u8 *)data;
|
|
|
|
|
2020-03-30 12:24:33 +00:00
|
|
|
in_buffer_lock.lock();
|
2018-09-15 19:41:54 +00:00
|
|
|
while (len > 0)
|
|
|
|
{
|
|
|
|
in_buffer.push(*p++);
|
|
|
|
len--;
|
|
|
|
}
|
2020-03-30 12:24:33 +00:00
|
|
|
in_buffer_lock.unlock();
|
2018-09-15 19:41:54 +00:00
|
|
|
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
void write_pico(u8 b)
|
|
|
|
{
|
2020-03-30 12:24:33 +00:00
|
|
|
out_buffer_lock.lock();
|
2018-09-15 19:41:54 +00:00
|
|
|
out_buffer.push(b);
|
2020-03-30 12:24:33 +00:00
|
|
|
out_buffer_lock.unlock();
|
2018-09-15 19:41:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int read_pico()
|
|
|
|
{
|
2020-03-30 12:24:33 +00:00
|
|
|
in_buffer_lock.lock();
|
2018-09-15 19:41:54 +00:00
|
|
|
if (in_buffer.empty())
|
2018-09-24 17:06:14 +00:00
|
|
|
{
|
2020-03-30 12:24:33 +00:00
|
|
|
in_buffer_lock.unlock();
|
2018-09-15 19:41:54 +00:00
|
|
|
return -1;
|
2018-09-24 17:06:14 +00:00
|
|
|
}
|
2018-09-15 19:41:54 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
u32 b = in_buffer.front();
|
|
|
|
in_buffer.pop();
|
2020-03-30 12:24:33 +00:00
|
|
|
in_buffer_lock.unlock();
|
2018-09-15 19:41:54 +00:00
|
|
|
return b;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-26 16:14:34 +00:00
|
|
|
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");
|
|
|
|
closesocket(nat_sock);
|
|
|
|
pico_socket_close(pico_sock);
|
|
|
|
tcp_sockets.erase(pico_sock);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-24 17:06:14 +00:00
|
|
|
static void tcp_callback(uint16_t ev, struct pico_socket *s)
|
|
|
|
{
|
|
|
|
int r = 0;
|
|
|
|
|
|
|
|
if (ev & PICO_SOCK_EV_RD)
|
|
|
|
{
|
|
|
|
auto it = tcp_sockets.find(s);
|
|
|
|
if (it == tcp_sockets.end())
|
|
|
|
{
|
2018-10-26 16:14:34 +00:00
|
|
|
if (tcp_connecting_sockets.find(s) == tcp_connecting_sockets.end())
|
2019-07-01 08:28:31 +00:00
|
|
|
INFO_LOG(MODEM, "Unknown socket: remote port %d", short_be(s->remote_port));
|
2018-09-24 17:06:14 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2018-10-26 16:14:34 +00:00
|
|
|
read_from_dc_socket(it->first, it->second);
|
2018-09-24 17:06:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ev & PICO_SOCK_EV_CONN)
|
|
|
|
{
|
|
|
|
uint32_t ka_val = 0;
|
|
|
|
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)
|
|
|
|
{
|
2018-10-26 16:14:34 +00:00
|
|
|
// Also called for child sockets
|
|
|
|
if (tcp_sockets.find(s) == tcp_sockets.end())
|
2019-07-01 08:28:31 +00:00
|
|
|
INFO_LOG(MODEM, "pico_socket_accept: %s\n", strerror(pico_err));
|
2018-09-24 17:06:14 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2019-08-14 07:17:07 +00:00
|
|
|
pico_ipv4_to_string(peer, sock_a->local_addr.ip4.addr);
|
2018-10-26 16:14:34 +00:00
|
|
|
//printf("Connection established from %s:%d to %08x:%d\n", peer, short_be(port), sock_a->local_addr.ip4.addr, short_be(sock_a->local_port));
|
2018-09-24 17:06:14 +00:00
|
|
|
pico_socket_setoption(sock_a, PICO_TCP_NODELAY, &yes);
|
|
|
|
/* Set keepalive options */
|
|
|
|
// 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);
|
|
|
|
|
2018-09-25 10:29:51 +00:00
|
|
|
sock_t sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
|
|
|
if (!VALID(sockfd))
|
2018-09-24 17:06:14 +00:00
|
|
|
{
|
|
|
|
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;
|
2019-08-14 07:17:07 +00:00
|
|
|
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
|
|
|
|
}
|
2018-09-24 17:06:14 +00:00
|
|
|
|
|
|
|
serveraddr.sin_port = sock_a->local_port;
|
2018-10-26 16:14:34 +00:00
|
|
|
set_non_blocking(sockfd);
|
2018-09-24 17:06:14 +00:00
|
|
|
if (connect(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0)
|
|
|
|
{
|
2018-10-26 16:14:34 +00:00
|
|
|
if (get_last_error() != EINPROGRESS && get_last_error() != L_EWOULDBLOCK)
|
|
|
|
{
|
|
|
|
pico_ipv4_to_string(peer, sock_a->local_addr.ip4.addr);
|
2019-07-01 08:28:31 +00:00
|
|
|
INFO_LOG(MODEM, "TCP connection to %s:%d failed: %s", peer, short_be(sock_a->local_port), strerror(get_last_error()));
|
2018-10-26 16:14:34 +00:00
|
|
|
closesocket(sockfd);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
tcp_connecting_sockets[sock_a] = sockfd;
|
2018-09-24 17:06:14 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2018-10-26 16:14:34 +00:00
|
|
|
set_tcp_nodelay(sockfd);
|
2018-09-24 17:06:14 +00:00
|
|
|
|
|
|
|
tcp_sockets[sock_a] = sockfd;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ev & PICO_SOCK_EV_FIN) {
|
|
|
|
auto it = tcp_sockets.find(s);
|
|
|
|
if (it == tcp_sockets.end())
|
|
|
|
{
|
2019-07-01 08:28:31 +00:00
|
|
|
INFO_LOG(MODEM, "PICO_SOCK_EV_FIN: Unknown socket: remote port %d", short_be(s->remote_port));
|
2018-09-24 17:06:14 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
closesocket(it->second);
|
2018-10-26 16:14:34 +00:00
|
|
|
tcp_sockets.erase(it);
|
2018-09-24 17:06:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ev & PICO_SOCK_EV_ERR) {
|
2019-07-01 08:28:31 +00:00
|
|
|
INFO_LOG(MODEM, "Socket error received: %s", strerror(pico_err));
|
2018-09-24 17:06:14 +00:00
|
|
|
auto it = tcp_sockets.find(s);
|
|
|
|
if (it == tcp_sockets.end())
|
|
|
|
{
|
2019-07-01 08:28:31 +00:00
|
|
|
INFO_LOG(MODEM, "PICO_SOCK_EV_ERR: Unknown socket: remote port %d", short_be(s->remote_port));
|
2018-09-24 17:06:14 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
closesocket(it->second);
|
2018-10-26 16:14:34 +00:00
|
|
|
tcp_sockets.erase(it);
|
2018-09-24 17:06:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-26 16:14:34 +00:00
|
|
|
if (ev & PICO_SOCK_EV_CLOSE)
|
|
|
|
{
|
|
|
|
auto it = tcp_sockets.find(s);
|
|
|
|
if (it == tcp_sockets.end())
|
|
|
|
{
|
2019-07-01 08:28:31 +00:00
|
|
|
INFO_LOG(MODEM, "PICO_SOCK_EV_CLOSE: Unknown socket: remote port %d", short_be(s->remote_port));
|
2018-10-26 16:14:34 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
shutdown(it->second, SHUT_WR);
|
|
|
|
pico_socket_shutdown(s, PICO_SHUT_RD);
|
|
|
|
}
|
|
|
|
}
|
2018-09-24 17:06:14 +00:00
|
|
|
|
|
|
|
// if (ev & PICO_SOCK_EV_WR)
|
|
|
|
// {
|
|
|
|
// }
|
|
|
|
}
|
|
|
|
|
2018-09-25 10:29:51 +00:00
|
|
|
static sock_t find_udp_socket(uint16_t src_port)
|
2018-09-24 17:06:14 +00:00
|
|
|
{
|
|
|
|
auto it = udp_sockets.find(src_port);
|
|
|
|
if (it != udp_sockets.end())
|
|
|
|
return it->second;
|
|
|
|
|
2018-09-25 10:29:51 +00:00
|
|
|
sock_t sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
|
|
if (!VALID(sockfd))
|
2018-09-24 17:06:14 +00:00
|
|
|
{
|
|
|
|
perror("socket");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
#ifndef _WIN32
|
|
|
|
fcntl(sockfd, F_SETFL, O_NONBLOCK);
|
|
|
|
#else
|
2018-09-25 10:29:51 +00:00
|
|
|
u_long optl = 1;
|
2018-09-24 17:06:14 +00:00
|
|
|
ioctlsocket(sockfd, FIONBIO, &optl);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// FIXME Need to clean up at some point?
|
|
|
|
udp_sockets[src_port] = sockfd;
|
|
|
|
|
|
|
|
return sockfd;
|
2018-09-15 19:41:54 +00:00
|
|
|
}
|
|
|
|
|
2018-09-24 17:06:14 +00:00
|
|
|
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)
|
2019-07-01 08:28:31 +00:00
|
|
|
INFO_LOG(MODEM, "error UDP recv: %s", strerror(pico_err));
|
2018-09-24 17:06:14 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2018-09-25 10:29:51 +00:00
|
|
|
sock_t sockfd = find_udp_socket(src_port);
|
|
|
|
if (VALID(sockfd))
|
2018-09-24 17:06:14 +00:00
|
|
|
{
|
|
|
|
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) {
|
2019-07-01 08:28:31 +00:00
|
|
|
INFO_LOG(MODEM, "UDP Callback error received");
|
2018-09-24 17:06:14 +00:00
|
|
|
}
|
|
|
|
}
|
2018-09-15 19:41:54 +00:00
|
|
|
|
2018-09-24 17:06:14 +00:00
|
|
|
static void read_native_sockets()
|
2018-09-15 19:41:54 +00:00
|
|
|
{
|
2018-09-24 17:06:14 +00:00
|
|
|
int r;
|
|
|
|
struct sockaddr_in src_addr;
|
|
|
|
socklen_t addr_len;
|
2018-09-15 19:41:54 +00:00
|
|
|
|
2018-09-24 17:06:14 +00:00
|
|
|
// Accept incoming TCP connections
|
2018-10-26 16:14:34 +00:00
|
|
|
for (auto it = tcp_listening_sockets.begin(); it != tcp_listening_sockets.end(); it++)
|
|
|
|
{
|
2018-09-24 17:06:14 +00:00
|
|
|
addr_len = sizeof(src_addr);
|
|
|
|
memset(&src_addr, 0, addr_len);
|
2018-10-26 16:14:34 +00:00
|
|
|
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);
|
2018-09-24 17:06:14 +00:00
|
|
|
if (ps == NULL)
|
|
|
|
{
|
2019-07-01 08:28:31 +00:00
|
|
|
INFO_LOG(MODEM, "pico_socket_open failed: error %d", pico_err);
|
2018-09-24 17:06:14 +00:00
|
|
|
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)
|
|
|
|
{
|
2019-07-01 08:28:31 +00:00
|
|
|
INFO_LOG(MODEM, "pico_socket_connect failed: error %d", pico_err);
|
2018-09-24 17:06:14 +00:00
|
|
|
closesocket(sockfd);
|
2018-09-24 19:19:43 +00:00
|
|
|
pico_socket_close(ps);
|
2018-09-24 17:06:14 +00:00
|
|
|
continue;
|
|
|
|
}
|
2018-10-26 16:14:34 +00:00
|
|
|
set_non_blocking(sockfd);
|
|
|
|
set_tcp_nodelay(sockfd);
|
2018-09-24 17:06:14 +00:00
|
|
|
tcp_sockets[ps] = sockfd;
|
2018-10-26 16:14:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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);
|
2020-03-29 17:29:14 +00:00
|
|
|
max_fd = std::max(max_fd, (int)it->second);
|
2018-10-26 16:14:34 +00:00
|
|
|
}
|
|
|
|
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;
|
|
|
|
}
|
2018-10-29 21:59:46 +00:00
|
|
|
#ifdef _WIN32
|
|
|
|
char value;
|
|
|
|
#else
|
2018-10-26 16:14:34 +00:00
|
|
|
int value;
|
2018-10-29 21:59:46 +00:00
|
|
|
#endif
|
2018-10-26 16:14:34 +00:00
|
|
|
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);
|
2019-07-01 08:28:31 +00:00
|
|
|
INFO_LOG(MODEM, "TCP connection to %s:%d failed: %s", peer, short_be(it->first->local_port), strerror(get_last_error()));
|
2018-10-26 16:14:34 +00:00
|
|
|
pico_socket_close(it->first);
|
|
|
|
closesocket(it->second);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
set_tcp_nodelay(it->second);
|
|
|
|
tcp_sockets[it->first] = it->second;
|
|
|
|
|
|
|
|
read_from_dc_socket(it->first, it->second);
|
|
|
|
}
|
|
|
|
it = tcp_connecting_sockets.erase(it);
|
|
|
|
}
|
|
|
|
}
|
2018-09-24 17:06:14 +00:00
|
|
|
|
|
|
|
char buf[1500]; // FIXME MTU ?
|
|
|
|
struct pico_msginfo msginfo;
|
|
|
|
|
|
|
|
// If modem buffer is full, wait
|
2020-03-30 12:24:33 +00:00
|
|
|
in_buffer_lock.lock();
|
2018-09-24 17:06:14 +00:00
|
|
|
size_t in_buffer_size = in_buffer.size();
|
2020-03-30 12:24:33 +00:00
|
|
|
in_buffer_lock.unlock();
|
2018-09-24 17:06:14 +00:00
|
|
|
if (in_buffer_size >= 256)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Read UDP sockets
|
|
|
|
for (auto it = udp_sockets.begin(); it != udp_sockets.end(); it++)
|
|
|
|
{
|
|
|
|
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;
|
2018-10-26 16:14:34 +00:00
|
|
|
//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));
|
2018-09-24 17:06:14 +00:00
|
|
|
int r2 = pico_socket_sendto_extended(pico_udp_socket, buf, r, &dcaddr, it->first, &msginfo);
|
|
|
|
if (r2 < r)
|
2019-07-01 08:28:31 +00:00
|
|
|
INFO_LOG(MODEM, "error UDP sending to %d: %s", short_be(it->first), strerror(pico_err));
|
2018-09-24 17:06:14 +00:00
|
|
|
}
|
2018-09-25 10:29:51 +00:00
|
|
|
else if (r < 0 && get_last_error() != L_EAGAIN && get_last_error() != L_EWOULDBLOCK)
|
2018-09-24 17:06:14 +00:00
|
|
|
{
|
2018-09-25 10:29:51 +00:00
|
|
|
perror("recvfrom udp socket");
|
2018-09-24 17:06:14 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read TCP sockets
|
2018-10-26 16:14:34 +00:00
|
|
|
for (auto it = tcp_sockets.begin(); it != tcp_sockets.end(); )
|
2018-09-24 17:06:14 +00:00
|
|
|
{
|
|
|
|
uint32_t space;
|
|
|
|
pico_tcp_get_bufspace_out(it->first, &space);
|
|
|
|
if (space < sizeof(buf))
|
|
|
|
{
|
|
|
|
// Wait for the out buffer to empty a bit
|
2018-10-26 16:14:34 +00:00
|
|
|
it++;
|
2018-09-24 17:06:14 +00:00
|
|
|
continue;
|
|
|
|
}
|
2018-09-25 10:29:51 +00:00
|
|
|
r = recv(it->second, buf, sizeof(buf), 0);
|
2018-09-24 17:06:14 +00:00
|
|
|
if (r > 0)
|
|
|
|
{
|
2018-10-26 16:14:34 +00:00
|
|
|
if (it->first->remote_port == short_be(5011) && r >= 5)
|
|
|
|
{
|
|
|
|
// Visual Concepts sport games
|
|
|
|
if (buf[0] == 1)
|
|
|
|
memcpy(&buf[1], &it->first->local_addr.ip4.addr, 4);
|
|
|
|
}
|
|
|
|
|
2018-09-24 17:06:14 +00:00
|
|
|
int r2 = pico_socket_send(it->first, buf, r);
|
|
|
|
if (r2 < 0)
|
2019-07-01 08:28:31 +00:00
|
|
|
INFO_LOG(MODEM, "error TCP sending: %s", strerror(pico_err));
|
2018-09-24 17:06:14 +00:00
|
|
|
else if (r2 < r)
|
|
|
|
// FIXME EAGAIN errors. Need to buffer data or wait for call back.
|
2019-07-01 08:28:31 +00:00
|
|
|
INFO_LOG(MODEM, "truncated send: %d -> %d", r, r2);
|
2018-09-24 17:06:14 +00:00
|
|
|
}
|
2018-09-25 22:04:57 +00:00
|
|
|
else if (r == 0)
|
|
|
|
{
|
2018-10-26 16:14:34 +00:00
|
|
|
pico_socket_shutdown(it->first, PICO_SHUT_WR);
|
|
|
|
shutdown(it->second, SHUT_RD);
|
2018-09-25 22:04:57 +00:00
|
|
|
}
|
2018-09-25 10:29:51 +00:00
|
|
|
else if (r < 0 && get_last_error() != L_EAGAIN && get_last_error() != L_EWOULDBLOCK)
|
2018-09-24 17:06:14 +00:00
|
|
|
{
|
2018-09-25 10:29:51 +00:00
|
|
|
perror("recv tcp socket");
|
2018-09-24 17:06:14 +00:00
|
|
|
closesocket(it->second);
|
|
|
|
pico_socket_close(it->first);
|
2018-10-26 16:14:34 +00:00
|
|
|
it = tcp_sockets.erase(it);
|
|
|
|
continue;
|
2018-09-24 17:06:14 +00:00
|
|
|
}
|
2018-10-26 16:14:34 +00:00
|
|
|
it++;
|
2018-09-24 17:06:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void close_native_sockets()
|
|
|
|
{
|
|
|
|
for (auto it = udp_sockets.begin(); it != udp_sockets.end(); it++)
|
|
|
|
closesocket(it->second);
|
|
|
|
udp_sockets.clear();
|
|
|
|
for (auto it = tcp_sockets.begin(); it != tcp_sockets.end(); it++)
|
|
|
|
{
|
|
|
|
pico_socket_close(it->first);
|
|
|
|
closesocket(it->second);
|
|
|
|
}
|
|
|
|
tcp_sockets.clear();
|
2018-10-26 17:38:59 +00:00
|
|
|
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();
|
2018-09-24 17:06:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static int modem_set_speed(struct pico_device *dev, uint32_t speed)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-09-25 10:29:51 +00:00
|
|
|
#if 0 // _WIN32
|
2018-09-24 17:06:14 +00:00
|
|
|
static void usleep(unsigned int usec)
|
|
|
|
{
|
|
|
|
HANDLE timer;
|
|
|
|
LARGE_INTEGER ft;
|
|
|
|
|
|
|
|
ft.QuadPart = -(10 * (__int64)usec);
|
|
|
|
|
|
|
|
timer = CreateWaitableTimer(NULL, TRUE, NULL);
|
|
|
|
SetWaitableTimer(timer, &ft, 0, NULL, NULL, 0);
|
|
|
|
WaitForSingleObject(timer, INFINITE);
|
|
|
|
CloseHandle(timer);
|
2018-09-15 19:41:54 +00:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2018-09-24 17:06:14 +00:00
|
|
|
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);
|
2019-07-01 08:28:31 +00:00
|
|
|
INFO_LOG(MODEM, "My IP is %s", myip);
|
2018-09-24 17:06:14 +00:00
|
|
|
}
|
|
|
|
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);
|
2019-07-01 08:28:31 +00:00
|
|
|
INFO_LOG(MODEM, "AFO server IP is %s", afoip);
|
2018-09-24 17:06:14 +00:00
|
|
|
}
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-15 19:41:54 +00:00
|
|
|
static bool pico_stack_inited;
|
2018-09-24 17:06:14 +00:00
|
|
|
static bool pico_thread_running = false;
|
2018-09-15 19:41:54 +00:00
|
|
|
|
2018-09-24 17:06:14 +00:00
|
|
|
static void *pico_thread_func(void *)
|
2018-09-15 19:41:54 +00:00
|
|
|
{
|
2018-09-24 17:06:14 +00:00
|
|
|
struct pico_ip4 ipaddr, netmask, zero = {
|
2018-09-15 19:41:54 +00:00
|
|
|
0
|
|
|
|
};
|
|
|
|
|
|
|
|
if (!pico_stack_inited)
|
|
|
|
{
|
|
|
|
pico_stack_init();
|
|
|
|
pico_stack_inited = true;
|
2020-03-29 17:29:14 +00:00
|
|
|
#if _WIN32
|
|
|
|
static WSADATA wsaData;
|
|
|
|
if (WSAStartup(MAKEWORD(2, 0), &wsaData) != 0)
|
|
|
|
WARN_LOG(MODEM, "WSAStartup failed");
|
|
|
|
#endif
|
2018-09-15 19:41:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// PPP
|
|
|
|
ppp = pico_ppp_create();
|
|
|
|
if (!ppp)
|
2018-09-24 17:06:14 +00:00
|
|
|
return NULL;
|
|
|
|
pico_string_to_ipv4("192.168.167.2", &dcaddr.addr);
|
2018-09-17 10:59:52 +00:00
|
|
|
pico_ppp_set_peer_ip(ppp, dcaddr);
|
2018-09-15 19:41:54 +00:00
|
|
|
pico_string_to_ipv4("192.168.167.1", &ipaddr.addr);
|
|
|
|
pico_ppp_set_ip(ppp, ipaddr);
|
|
|
|
|
2020-04-14 15:43:11 +00:00
|
|
|
pico_string_to_ipv4(settings.network.dns.c_str(), &dnsaddr.addr);
|
2018-09-17 10:59:52 +00:00
|
|
|
pico_ppp_set_dns1(ppp, dnsaddr);
|
2018-09-15 19:41:54 +00:00
|
|
|
|
2018-09-24 17:06:14 +00:00
|
|
|
pico_udp_socket = pico_socket_open(PICO_PROTO_IPV4, PICO_PROTO_UDP, &udp_callback);
|
|
|
|
if (pico_udp_socket == NULL) {
|
2019-07-01 08:28:31 +00:00
|
|
|
INFO_LOG(MODEM, "error opening UDP socket: %s", strerror(pico_err));
|
2018-09-24 17:06:14 +00:00
|
|
|
}
|
|
|
|
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)
|
2019-07-01 08:28:31 +00:00
|
|
|
INFO_LOG(MODEM, "error binding UDP socket to port %u: %s", short_be(listen_port), strerror(pico_err));
|
2018-09-24 17:06:14 +00:00
|
|
|
|
|
|
|
pico_tcp_socket = pico_socket_open(PICO_PROTO_IPV4, PICO_PROTO_TCP, &tcp_callback);
|
|
|
|
if (pico_tcp_socket == NULL) {
|
2019-07-01 08:28:31 +00:00
|
|
|
INFO_LOG(MODEM, "error opening TCP socket: %s", strerror(pico_err));
|
2018-09-24 17:06:14 +00:00
|
|
|
}
|
|
|
|
pico_socket_setoption(pico_tcp_socket, PICO_TCP_NODELAY, &yes);
|
|
|
|
ret = pico_socket_bind(pico_tcp_socket, &inaddr_any, &listen_port);
|
|
|
|
if (ret < 0) {
|
2019-07-01 08:28:31 +00:00
|
|
|
INFO_LOG(MODEM, "error binding TCP socket to port %u: %s", short_be(listen_port), strerror(pico_err));
|
2018-09-15 19:41:54 +00:00
|
|
|
}
|
2018-09-17 10:59:52 +00:00
|
|
|
else
|
|
|
|
{
|
2018-09-24 17:06:14 +00:00
|
|
|
if (pico_socket_listen(pico_tcp_socket, 10) != 0)
|
2019-07-01 08:28:31 +00:00
|
|
|
INFO_LOG(MODEM, "error listening on port %u", short_be(listen_port));
|
2018-09-17 10:59:52 +00:00
|
|
|
}
|
2018-09-24 17:06:14 +00:00
|
|
|
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 (int i = 0; i < sizeof(games_udp_ports) / sizeof(uint16_t); i++)
|
2018-09-17 10:59:52 +00:00
|
|
|
{
|
2018-09-24 17:06:14 +00:00
|
|
|
uint16_t port = short_be(games_udp_ports[i]);
|
2018-09-25 10:29:51 +00:00
|
|
|
sock_t sockfd = find_udp_socket(port);
|
2018-09-24 17:06:14 +00:00
|
|
|
saddr.sin_port = port;
|
2018-09-17 10:59:52 +00:00
|
|
|
|
2018-09-24 19:37:07 +00:00
|
|
|
if (::bind(sockfd, (struct sockaddr *)&saddr, saddr_len) < 0)
|
|
|
|
{
|
|
|
|
perror("bind");
|
|
|
|
continue;
|
|
|
|
}
|
2018-09-24 17:06:14 +00:00
|
|
|
}
|
2018-09-15 19:41:54 +00:00
|
|
|
|
2018-09-24 17:06:14 +00:00
|
|
|
for (int i = 0; i < sizeof(games_tcp_ports) / sizeof(uint16_t); i++)
|
2018-09-15 19:41:54 +00:00
|
|
|
{
|
2018-09-24 17:06:14 +00:00
|
|
|
uint16_t port = short_be(games_tcp_ports[i]);
|
|
|
|
saddr.sin_port = port;
|
2018-09-25 10:29:51 +00:00
|
|
|
sock_t sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
2018-09-24 17:06:14 +00:00
|
|
|
if (::bind(sockfd, (struct sockaddr *)&saddr, saddr_len) < 0)
|
|
|
|
{
|
|
|
|
perror("bind");
|
2018-09-24 19:19:43 +00:00
|
|
|
closesocket(sockfd);
|
2018-09-24 17:06:14 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (listen(sockfd, 5) < 0)
|
|
|
|
{
|
|
|
|
perror("listen");
|
2018-09-24 19:19:43 +00:00
|
|
|
closesocket(sockfd);
|
2018-09-24 17:06:14 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
set_non_blocking(sockfd);
|
|
|
|
tcp_listening_sockets[port] = sockfd;
|
2018-09-15 19:41:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
2018-09-24 17:06:14 +00:00
|
|
|
while (pico_thread_running)
|
2018-09-15 19:41:54 +00:00
|
|
|
{
|
2018-09-24 17:06:14 +00:00
|
|
|
read_native_sockets();
|
|
|
|
pico_stack_tick();
|
|
|
|
check_dns_entries();
|
|
|
|
usleep(1000);
|
2018-09-15 19:41:54 +00:00
|
|
|
}
|
|
|
|
|
2018-09-24 17:06:14 +00:00
|
|
|
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);
|
2018-09-15 19:41:54 +00:00
|
|
|
|
|
|
|
if (ppp)
|
|
|
|
{
|
|
|
|
pico_ppp_destroy(ppp);
|
|
|
|
ppp = NULL;
|
|
|
|
}
|
2018-09-24 19:19:43 +00:00
|
|
|
pico_stack_tick();
|
2018-09-24 17:06:14 +00:00
|
|
|
|
|
|
|
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();
|
2018-09-15 19:41:54 +00:00
|
|
|
}
|
2018-09-17 16:14:48 +00:00
|
|
|
|
|
|
|
#else
|
|
|
|
|
2018-09-17 16:45:11 +00:00
|
|
|
#include "types.h"
|
|
|
|
|
2018-09-17 16:14:48 +00:00
|
|
|
bool start_pico() { return false; }
|
2018-09-17 17:43:05 +00:00
|
|
|
void stop_pico() { }
|
|
|
|
void write_pico(u8 b) { }
|
2018-09-17 16:14:48 +00:00
|
|
|
int read_pico() { return -1; }
|
|
|
|
|
|
|
|
#endif
|