/*
Copyright 2025 flyinghead
This file is part of Flycast.
Flycast is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Flycast is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Flycast. If not, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define DEFAULT_TTY "/dev/ttyACM0"
#define DEFAULT_HOST "dcnet.flyca.st"
#define DEFAULT_PORT 7654
char ttyName[512] = DEFAULT_TTY;
int useUdp;
char hostName[64] = DEFAULT_HOST;
uint16_t port = DEFAULT_PORT;
struct termios tbufsave;
int setNonBlocking(int fd)
{
int flags = fcntl(fd, F_GETFL, 0);
if (flags == -1)
flags = 0;
flags |= O_NONBLOCK;
if (fcntl(fd, F_SETFL, flags) != 0) {
perror("fcntl(O_NONBLOCK)");
return -1;
}
return 0;
}
int configureTty(int fd, int local)
{
if (tcgetattr(fd, &tbufsave) == -1)
perror("tcgetattr");
struct termios tbuf;
memcpy(&tbuf, &tbufsave, sizeof(tbuf));
/* 8-bit, one stop bit, no parity, carrier detect, no hang up on close,
disable RTS/CTS flow control.
*/
tbuf.c_cflag &= ~(CSIZE | CSTOPB | PARENB | CLOCAL | HUPCL | CRTSCTS);
tbuf.c_cflag |= CS8 | CREAD;
if (local)
// ignore CD
tbuf.c_cflag |= CLOCAL;
/* don't translate NL to CR or CR to NL on input, get all 8 bits of input
disable xon/xoff flow control on output, no interrupt on break signal,
ignore parity, ignore break
*/
tbuf.c_iflag = IGNBRK | IGNPAR;
/* disable all output processing */
tbuf.c_oflag = 0;
/* non-canonical, ignore signals and no echoing on output */
tbuf.c_lflag = 0;
tbuf.c_cc[VMIN] = 1;
tbuf.c_cc[VTIME] = 0;
/* set the parameters associated with the terminal port */
if (tcsetattr(fd, TCSANOW, &tbuf) == -1) {
perror("tcsetattr");
return 1;
}
return 0;
}
int main(int argc, char *argv[])
{
int opt;
while ((opt = getopt(argc, argv, "t:h:p:")) != -1)
{
switch (opt)
{
/*
case 'u':
useUdp = 1;
break;
*/
case 't':
strcpy(ttyName, optarg);
break;
case 'h':
strcpy(hostName, optarg);
break;
case 'p':
port = (uint16_t)atoi(optarg);
break;
default:
fprintf(stderr, "Usage: %s [-t ] [-h ]\n", argv[0]);
fprintf(stderr, "Default tty is %s. Default host:port is %s:%d\n", DEFAULT_TTY, DEFAULT_HOST, DEFAULT_PORT);
exit(1);
}
}
fprintf(stderr, "DCNet starting\n");
/* Resolve server name */
struct addrinfo hints, *result;
memset(&hints, 0, sizeof (hints));
hints.ai_family = AF_INET;
hints.ai_socktype = useUdp ? SOCK_DGRAM : SOCK_STREAM;
hints.ai_flags |= AI_CANONNAME;
int errcode = getaddrinfo(hostName, NULL, &hints, &result);
if (errcode != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(errcode));
return -1;
}
if (result == NULL) {
fprintf(stderr, "%s: host not found\n", hostName);
return -1;
}
char s[100];
struct sockaddr_in *serverAddress = (struct sockaddr_in *)result->ai_addr;
inet_ntop(result->ai_family, &serverAddress->sin_addr, s, 100);
printf("%s is %s (%s)\n", hostName, s, result->ai_canonname);
/* TTY */
int ttyfd = open(ttyName, O_RDWR);
if (ttyfd == -1) {
perror("Can't open tty");
return 1;
}
if (configureTty(ttyfd, 0))
return 1;
/* SOCKET */
int sockfd = socket(AF_INET, useUdp ? SOCK_DGRAM : SOCK_STREAM, 0);
if (sockfd == -1) {
fprintf(stderr, "socket() failed: %d\n", errno);
return 1;
}
serverAddress->sin_port = htons(port);
if (connect(sockfd, (struct sockaddr *)serverAddress, sizeof(*serverAddress)) != 0) {
fprintf(stderr, "connect() failed: %d\n", errno);
return 1;
}
freeaddrinfo(result);
if (setNonBlocking(sockfd) || setNonBlocking(ttyfd))
return -1;
char outbuf[1504];
ssize_t outbuflen = 0;
char inbuf[1504];
ssize_t inbuflen = 0;
for (;;)
{
#ifdef DEBUG
ssize_t old_olen = outbuflen;
ssize_t old_ilen = inbuflen;
#endif
fd_set readfds;
FD_ZERO(&readfds);
if (inbuflen < sizeof(inbuf))
FD_SET(sockfd, &readfds);
if (outbuflen < sizeof(outbuf))
FD_SET(ttyfd, &readfds);
fd_set writefds;
FD_ZERO(&writefds);
ssize_t outbufReady = 0;
if (outbuflen > 0)
{
outbufReady = outbuflen;
/*
if (outbuf[0] != 0x7e) {
outbufReady = outbuflen;
}
else {
for (int i = 1; i < outbuflen; i++)
{
if (outbuf[i] == 0x7e) {
outbufReady = i + 1;
break;
}
}
}
*/
if (outbufReady != 0)
FD_SET(sockfd, &writefds);
}
if (inbuflen > 0)
FD_SET(ttyfd, &writefds);
int nfds = (sockfd > ttyfd ? sockfd : ttyfd) + 1;
if (select(nfds, &readfds, &writefds, NULL, NULL) == -1)
{
if (errno == EINTR)
continue;
fprintf(stderr, "select() failed: %d\n", errno);
close(sockfd);
return 1;
}
if (FD_ISSET(ttyfd, &readfds))
{
ssize_t ret = read(ttyfd, outbuf + outbuflen, sizeof(outbuf) - (size_t)outbuflen);
if (ret < 0) {
if (errno != EINTR && errno != EWOULDBLOCK) {
fprintf(stderr, "read from tty failed: %d\n", errno);
break;
}
ret = 0;
}
else if (ret == 0) {
fprintf(stderr, "modem hang up\n");
break;
}
if (ret > 0) {
outbuflen += ret;
FD_SET(sockfd, &writefds);
}
}
if (FD_ISSET(sockfd, &readfds))
{
ssize_t ret = read(sockfd, inbuf + inbuflen, sizeof(inbuf) - (size_t)inbuflen);
if (ret < 0) {
if (errno != EINTR && errno != EWOULDBLOCK) {
fprintf(stderr, "read from socket failed: %d\n", errno);
break;
}
ret = 0;
}
else if (ret == 0) {
fprintf(stderr, "socket read EOF\n");
break;
}
if (ret > 0) {
inbuflen += ret;
FD_SET(ttyfd, &writefds);
}
}
if (FD_ISSET(ttyfd, &writefds))
{
ssize_t ret = write(ttyfd, inbuf, (size_t)inbuflen);
if (ret < 0) {
if (errno != EINTR && errno != EWOULDBLOCK) {
fprintf(stderr, "write to tty failed: %d\n", errno);
break;
}
ret = 0;
}
if (ret > 0)
{
inbuflen -= ret;
if (inbuflen > 0)
memmove(inbuf, inbuf + ret, (size_t)inbuflen);
}
}
if (FD_ISSET(sockfd, &writefds))
{
ssize_t ret = write(sockfd, outbuf, (size_t)outbufReady);
if (ret < 0) {
if (errno == EINTR && errno != EWOULDBLOCK) {
fprintf(stderr, "write to socket failed: %d\n", errno);
break;
}
ret = 0;
}
if (ret > 0)
{
outbuflen -= ret;
if (outbuflen > 0)
memmove(outbuf, outbuf + ret, (size_t)outbuflen);
}
}
#ifdef DEBUG
printf("OUT %03zd%c IN %03zd%c\r",
outbuflen, outbuflen > old_olen ? '+' : outbuflen < old_olen ? '-' : ' ',
inbuflen, inbuflen > old_ilen ? '+' : inbuflen < old_ilen ? '-' : ' ');
fflush(stdout);
#endif
}
close(ttyfd);
ttyfd = open(ttyName, O_RDWR);
if (ttyfd == -1) {
perror("Can't reopen tty");
}
else {
tcsetattr(ttyfd, TCSANOW, &tbufsave);
close(ttyfd);
}
close(sockfd);
fprintf(stderr, "DCNet stopped\n");
return 0;
}