--- /dev/null
+/*
+ * UAE - The Un*x Amiga Emulator
+ *
+ * bsdsocket.library emulation - Unix
+ *
+ * Copyright 2000-2001 Carl Drougge <carl.drougge@home.se> <bearded@longhaired.org>
+ * Copyright 2003-2005 Richard Drummond
+ * Copyright 2004 Jeff Shepherd
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "sysconfig.h"
+#include "sysdeps.h"
+
+#include <atomic>
+
+#include "options.h"
+#include "memory.h"
+#include "newcpu.h"
+#include "autoconf.h"
+#include "traps.h"
+#include "threaddep/thread.h"
+#include "native2amiga.h"
+#include "bsdsocket.h"
+#include "uae.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/select.h>
+#include <sys/ioctl.h>
+#ifdef HAVE_SYS_FILIO_H
+# include <sys/filio.h>
+#endif
+#include <net/if.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <netdb.h>
+#include <csignal>
+#include <arpa/inet.h>
+#include <unistd.h>
+
+#if defined(__HAIKU__)
+/* Haiku does not define these obsolete/rare constants */
+#ifndef ESOCKTNOSUPPORT
+#define ESOCKTNOSUPPORT 44
+#endif
+#ifndef ETOOMANYREFS
+#define ETOOMANYREFS 59
+#endif
+#ifndef IPPROTO_EGP
+#define IPPROTO_EGP 8
+#endif
+#ifndef IPPROTO_PUP
+#define IPPROTO_PUP 12
+#endif
+#ifndef IPPROTO_IDP
+#define IPPROTO_IDP 22
+#endif
+#ifndef IPPROTO_ENCAP
+#define IPPROTO_ENCAP 98
+#endif
+#ifndef SIOCGIFCONF
+#include <sys/sockio.h>
+#endif
+#endif /* __HAIKU__ */
+
+#include <cstddef>
+#include <cstring>
+#include <vector>
+#include <mutex>
+static std::mutex bsdsock_mutex;
+
+#define close_socket close
+#define close_pipe(fd) close(fd)
+#define write_pipe(fd, buf, len) write(fd, buf, len)
+#define read_pipe(fd, buf, len) read(fd, buf, len)
+
+#define WAITSIGNAL waitsig (ctx, sb)
+
+/* Sigqueue is unsafe on SMP machines.
+ * Temporary work-around.
+ */
+#define SETSIGNAL \
+ do { \
+ uae_Signal (sb->ownertask, sb->sigstosend | ((uae_u32) 1) << sb->signal); \
+ sb->dosignal = 1; \
+ } while (0)
+
+
+#define SETERRNO bsdsocklib_seterrno (ctx, sb,mapErrno (errno))
+#define SETHERRNO bsdsocklib_setherrno (ctx, sb, h_errno)
+
+
+/* BSD-systems don't seem to have MSG_NOSIGNAL..
+ @@@ We need to catch SIGPIPE on those systems! (?) */
+#ifndef MSG_NOSIGNAL
+#define MSG_NOSIGNAL 0
+#endif
+
+#define S_GL_result(res) sb->resultval = (res)
+
+uae_u32 bsdthr_Accept_2 (SB);
+uae_u32 bsdthr_Recv_2 (SB);
+uae_u32 bsdthr_blockingstuff (uae_u32 (*tryfunc)(SB), SB);
+uae_u32 bsdthr_SendRecvAcceptConnect (uae_u32 (*tryfunc)(SB), SB);
+uae_u32 bsdthr_Send_2 (SB);
+uae_u32 bsdthr_Connect_2 (SB);
+uae_u32 bsdthr_WaitSelect (SB);
+uae_u32 bsdthr_Wait (SB);
+void clearsockabort (SB);
+
+static uae_sem_t sem_queue;
+
+/**
+ ** Socket Event Monitoring System
+ ** Monitors sockets with SO_EVENTMASK set and posts Amiga signals when events occur
+ **/
+
+// Entry for a socket being monitored for events
+struct socket_event_entry {
+ struct socketbase* sb;
+ int sd; // Amiga socket descriptor (0-based)
+ SOCKET_TYPE s; // Host socket
+ int eventmask; // REP_* flags to monitor
+ bool connecting; // True if connect() is in progress
+ bool connected; // True if socket is connected (or connectionless/listener)
+ int fired_mask; // Events that have fired and need re-enabling
+};
+
+// Event monitor thread state
+struct event_monitor {
+ uae_thread_id thread; // Monitor thread
+ std::mutex mutex; // Protects socket_list
+ int wake_pipe[2]; // Pipe to wake thread on changes
+ std::atomic<bool> running; // Thread running flag
+ std::vector<socket_event_entry> socket_list; // Sockets to monitor
+};
+
+static struct event_monitor* g_event_monitor = nullptr;
+
+/**
+ ** Helper functions
+ **/
+
+/*
+ * Map host errno to amiga errno
+ */
+static int mapErrno (int e)
+{
+ switch (e) {
+ case EINTR: e = 4; break;
+ case EDEADLK: e = 11; break;
+ case EAGAIN: e = 35; break;
+ case EINPROGRESS: e = 36; break;
+ case EALREADY: e = 37; break;
+ case ENOTSOCK: e = 38; break;
+ case EDESTADDRREQ: e = 39; break;
+ case EMSGSIZE: e = 40; break;
+ case EPROTOTYPE: e = 41; break;
+ case ENOPROTOOPT: e = 42; break;
+ case EPROTONOSUPPORT: e = 43; break;
+ case ESOCKTNOSUPPORT: e = 44; break;
+ case EOPNOTSUPP: e = 45; break;
+ case EPFNOSUPPORT: e = 46; break;
+ case EAFNOSUPPORT: e = 47; break;
+ case EADDRINUSE: e = 48; break;
+ case EADDRNOTAVAIL: e = 49; break;
+ case ENETDOWN: e = 50; break;
+ case ENETUNREACH: e = 51; break;
+ case ENETRESET: e = 52; break;
+ case ECONNABORTED: e = 53; break;
+ case ECONNRESET: e = 54; break;
+ case ENOBUFS: e = 55; break;
+ case EISCONN: e = 56; break;
+ case ENOTCONN: e = 57; break;
+ case ESHUTDOWN: e = 58; break;
+ case ETOOMANYREFS: e = 59; break;
+ case ETIMEDOUT: e = 60; break;
+ case ECONNREFUSED: e = 61; break;
+ case ELOOP: e = 62; break;
+ case ENAMETOOLONG: e = 63; break;
+ default: break;
+ }
+ return e;
+}
+
+/*
+ * Map amiga (s|g)etsockopt level into native one
+ */
+static int mapsockoptlevel (int level)
+{
+ switch (level) {
+ case 0xffff:
+ return SOL_SOCKET;
+ case 0:
+ return IPPROTO_IP;
+ case 1:
+ return IPPROTO_ICMP;
+ case 2:
+ return IPPROTO_IGMP;
+#ifdef IPPROTO_IPIP
+ case 4:
+ return IPPROTO_IPIP;
+#endif
+ case 6:
+ return IPPROTO_TCP;
+ case 8:
+ return IPPROTO_EGP;
+ case 12:
+ return IPPROTO_PUP;
+ case 17:
+ return IPPROTO_UDP;
+ case 22:
+ return IPPROTO_IDP;
+#ifdef IPPROTO_TP
+ case 29:
+ return IPPROTO_TP;
+#endif
+ case 98:
+ return IPPROTO_ENCAP;
+ default:
+ write_log ("Unknown sockopt level %d\n", level);
+ return level;
+ }
+}
+
+/*
+ * Map amiga (s|g)etsockopt optname into native one
+ */
+static int mapsockoptname (int level, int optname)
+{
+ switch (level) {
+
+ case SOL_SOCKET:
+ switch (optname) {
+ case 0x0001:
+ return SO_DEBUG;
+ case 0x0002:
+ return SO_ACCEPTCONN;
+ case 0x0004:
+ return SO_REUSEADDR;
+ case 0x0008:
+ return SO_KEEPALIVE;
+ case 0x0010:
+ return SO_DONTROUTE;
+ case 0x0020:
+ return SO_BROADCAST;
+#ifdef SO_USELOOPBACK
+ case 0x0040:
+ return SO_USELOOPBACK;
+#endif
+ case 0x0080:
+ return SO_LINGER;
+ case 0x0100:
+ return SO_OOBINLINE;
+#ifdef SO_REUSEPORT
+ case 0x0200:
+ return SO_REUSEPORT;
+#endif
+ case 0x1001:
+ return SO_SNDBUF;
+ case 0x1002:
+ return SO_RCVBUF;
+ case 0x1003:
+ return SO_SNDLOWAT;
+ case 0x1004:
+ return SO_RCVLOWAT;
+ case 0x1005:
+ return SO_SNDTIMEO;
+ case 0x1006:
+ return SO_RCVTIMEO;
+ case 0x1007:
+ return SO_ERROR;
+ case 0x1008:
+ return SO_TYPE;
+
+ default:
+ write_log("Invalid setsockopt option 0x%x for level %d\n",
+ optname, level);
+ return -1;
+ }
+ break;
+
+ case IPPROTO_IP:
+ switch (optname) {
+ case 1:
+ return IP_OPTIONS;
+ case 2:
+ return IP_HDRINCL;
+ case 3:
+ return IP_TOS;
+ case 4:
+ return IP_TTL;
+ case 5:
+ return IP_RECVOPTS;
+ case 6:
+ return IP_RECVRETOPTS;
+ case 8:
+ return IP_RETOPTS;
+ case 9:
+ return IP_MULTICAST_IF;
+ case 10:
+ return IP_MULTICAST_TTL;
+ case 11:
+ return IP_MULTICAST_LOOP;
+ case 12:
+ return IP_ADD_MEMBERSHIP;
+
+ default:
+ write_log("Invalid setsockopt option 0x%x for level %d\n",
+ optname, level);
+ return -1;
+ }
+ break;
+
+ case IPPROTO_TCP:
+ switch (optname) {
+ case 1:
+ return TCP_NODELAY;
+ case 2:
+ return TCP_MAXSEG;
+
+ default:
+ write_log("Invalid setsockopt option 0x%x for level %d\n",
+ optname, level);
+ return -1;
+ }
+ break;
+
+ default:
+ write_log("Unknown level %d\n", level);
+ return -1;
+ }
+}
+
+/*
+ * Map amiga (s|g)etsockopt return value into the correct form
+ */
+static void mapsockoptreturn(int level, int optname, uae_u32 optval, void *buf)
+{
+ switch (level) {
+
+ case SOL_SOCKET:
+ switch (optname) {
+ case SO_DEBUG:
+ case SO_ACCEPTCONN:
+ case SO_REUSEADDR:
+ case SO_KEEPALIVE:
+ case SO_DONTROUTE:
+ case SO_BROADCAST:
+#ifdef SO_USELOOPBACK
+ case SO_USELOOPBACK:
+#endif
+ case SO_LINGER:
+ case SO_OOBINLINE:
+#ifdef SO_REUSEPORT
+ case SO_REUSEPORT:
+#endif
+ case SO_SNDBUF:
+ case SO_RCVBUF:
+ case SO_SNDLOWAT:
+ case SO_RCVLOWAT:
+ case SO_SNDTIMEO:
+ case SO_RCVTIMEO:
+ case SO_TYPE:
+ put_long (optval, *(int *)buf);
+ break;
+
+ case SO_ERROR:
+ write_log("New errno is %d\n", mapErrno(*(int *)buf));
+ put_long (optval, mapErrno(*(int *)buf));
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case IPPROTO_IP:
+ switch (optname) {
+ case IP_OPTIONS:
+ case IP_HDRINCL:
+ case IP_TOS:
+ case IP_TTL:
+ case IP_RECVOPTS:
+ //case IP_RECVRETOPTS:
+ //case IP_RETOPTS:
+ case IP_MULTICAST_IF:
+ case IP_MULTICAST_TTL:
+ case IP_MULTICAST_LOOP:
+ case IP_ADD_MEMBERSHIP:
+ put_long (optval, *(int *)buf);
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case IPPROTO_TCP:
+ switch (optname) {
+ case TCP_NODELAY:
+ case TCP_MAXSEG:
+ put_long (optval,*(int *)buf);
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+/*
+ * Map amiga (s|g)etsockopt value from amiga to the appropriate value
+ */
+static void mapsockoptvalue(int level, int optname, uae_u32 optval, void *buf)
+{
+ switch (level) {
+
+ case SOL_SOCKET:
+ switch (optname) {
+ case SO_DEBUG:
+ case SO_ACCEPTCONN:
+ case SO_REUSEADDR:
+ case SO_KEEPALIVE:
+ case SO_DONTROUTE:
+ case SO_BROADCAST:
+#ifdef SO_USELOOPBACK
+ case SO_USELOOPBACK:
+#endif
+ case SO_LINGER:
+ case SO_OOBINLINE:
+#ifdef SO_REUSEPORT
+ case SO_REUSEPORT:
+#endif
+ case SO_SNDBUF:
+ case SO_RCVBUF:
+ case SO_SNDLOWAT:
+ case SO_RCVLOWAT:
+ case SO_SNDTIMEO:
+ case SO_RCVTIMEO:
+ case SO_TYPE:
+ case SO_ERROR:
+ *((int *)buf) = get_long (optval);
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case IPPROTO_IP:
+ switch (optname) {
+ case IP_OPTIONS:
+ case IP_HDRINCL:
+ case IP_TOS:
+ case IP_TTL:
+ case IP_RECVOPTS:
+ //case IP_RECVRETOPTS:
+ //case IP_RETOPTS:
+ case IP_MULTICAST_IF:
+ case IP_MULTICAST_TTL:
+ case IP_MULTICAST_LOOP:
+ case IP_ADD_MEMBERSHIP:
+ *((int *)buf) = get_long (optval);
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case IPPROTO_TCP:
+ switch (optname) {
+ case TCP_NODELAY:
+ case TCP_MAXSEG:
+ *((int *)buf) = get_long (optval);
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+STATIC_INLINE int bsd_amigaside_FD_ISSET (int n, uae_u32 set)
+{
+ uae_u32 foo = get_long (set + (n / 32));
+ if (foo & (1 << (n % 32)))
+ return 1;
+ return 0;
+}
+
+STATIC_INLINE void bsd_amigaside_FD_ZERO (uae_u32 set, int nfds)
+{
+ unsigned int i;
+ for (i = 0; i < (unsigned int)nfds; i += 32, set += 4)
+ put_long (set, 0);
+}
+
+STATIC_INLINE void bsd_amigaside_FD_SET (int n, uae_u32 set)
+{
+ set = set + (n / 32);
+ put_long (set, get_long (set) | (1 << (n % 32)));
+}
+
+static void printSockAddr(struct sockaddr_in* in)
+{
+ write_log("Family %d, ", in->sin_family);
+ write_log("Port %d,", ntohs(in->sin_port));
+ write_log("Address %s,", inet_ntoa(in->sin_addr));
+}
+
+/**
+ ** Socket Event Monitoring Functions
+ **/
+
+// Post an Amiga signal when a socket event occurs
+static void post_socket_event(struct socketbase* sb, int sd, int event_type)
+{
+ if (!sb || sd < 0) return;
+
+ // Verify socket still has an active event mask: race with SO_EVENTMASK=0
+ if (!(sb->ftable[sd] & REP_ALL)) return;
+
+ // Set the appropriate SET_* flag in ftable
+ sb->ftable[sd] |= (event_type << 8);
+
+ // Signal the Amiga task
+ addtosigqueue(sb, 1);
+}
+
+static int peek_socket(int s)
+{
+ char buf[1];
+ int res;
+ // Peek 1 byte without waiting
+ res = recv(s, buf, 1, MSG_PEEK | MSG_DONTWAIT);
+ if (res > 0) return 2; // Data available
+ if (res == 0) return 1; // EOF (Ready to read 0 bytes)
+ return 0; // Error/Spurious/WouldBlock
+}
+
+// Event monitor thread - monitors sockets and posts signals
+static void event_monitor_thread(void* data)
+{
+ struct event_monitor* monitor = (struct event_monitor*)data;
+
+ write_log("BSDSOCK: Event monitor thread started\n");
+
+ while (monitor->running) {
+ fd_set readfds, writefds, exceptfds;
+ int maxfd = monitor->wake_pipe[0];
+ struct timeval timeout;
+
+ FD_ZERO(&readfds);
+ FD_ZERO(&writefds);
+ FD_ZERO(&exceptfds);
+
+ // Always monitor wake pipe
+ FD_SET(monitor->wake_pipe[0], &readfds);
+
+ // Lock mutex to build fd_sets from socket list
+ monitor->mutex.lock();
+
+ if (!monitor->socket_list.empty()) {
+ BSDTRACE((_T("BSDSOCK: Event monitor checking %d sockets\n"), (int)monitor->socket_list.size()));
+ }
+
+ for (const auto& entry : monitor->socket_list) {
+ if (entry.s == INVALID_SOCKET) continue;
+
+ // Skip sockets with no events to monitor
+ if (entry.eventmask == 0) continue;
+
+ if (entry.s > maxfd) {
+ maxfd = entry.s;
+ }
+
+ // Add socket to appropriate fd_sets based on event mask
+ // Filter out events that have already fired (one-shot) for default handling
+ int active_mask = entry.eventmask & ~entry.fired_mask;
+
+ // Use active_mask to respect One-Shot behavior (Wait for re-enablement via recv/accept)
+ if (active_mask & (REP_READ | REP_ACCEPT)) {
+ if (active_mask & REP_READ) {
+ // Prevent premature monitoring of READ on connecting/disconnected sockets
+ if (!entry.connecting && entry.connected) {
+ FD_SET(entry.s, &readfds);
+ }
+ } else {
+ // REP_ACCEPT always monitored (if in active_mask)
+ FD_SET(entry.s, &readfds);
+ BSDTRACE((_T("BSDSOCK: Adding socket %d to readfds (mask has REP_ACCEPT)\n"), entry.sd));
+ }
+ }
+
+ // REP_CLOSE requires readfds to detect EOF via peek_socket
+ if ((active_mask & REP_CLOSE) && entry.connected && !entry.connecting) {
+ FD_SET(entry.s, &readfds);
+ }
+
+ // REP_WRITE is treated as Level Triggered in select() but Edge Triggered/One-Shot for Amiga signals.
+ // If connected and not connecting, we monitor for write if the event is active (not fired).
+ if ((active_mask & (REP_WRITE | REP_CONNECT)) && entry.connected && !entry.connecting) {
+ FD_SET(entry.s, &writefds);
+ }
+
+ // REP_CONNECT is One-Shot (handled via active_mask).
+ // Only monitor if explicitly connecting.
+ if ((active_mask & REP_CONNECT) && entry.connecting) {
+ FD_SET(entry.s, &writefds);
+ BSDTRACE((_T("BSDSOCK: Monitoring socket %d for connect completion (connecting=true)\n"), entry.sd));
+ }
+
+ if (active_mask & REP_OOB) {
+ FD_SET(entry.s, &exceptfds);
+ }
+ }
+
+ monitor->mutex.unlock();
+
+ // Wait for events with 1 second timeout
+ timeout.tv_sec = 1;
+ timeout.tv_usec = 0;
+
+ int result = select(maxfd + 1, &readfds, &writefds, &exceptfds, &timeout);
+
+ BSDTRACE((_T("BSDSOCK: select() returned %d\n"), result));
+
+ if (result < 0) {
+ if (errno == EINTR) continue;
+ if (errno == EBADF) {
+ BSDTRACE((_T("BSDSOCK: Event monitor select() got EBADF, rebuilding\n")));
+ continue;
+ }
+ write_log("BSDSOCK: Event monitor select() error: %d\n", errno);
+ Sleep(100);
+ continue;
+ }
+
+ if (result == 0) {
+ // Timeout, just loop again
+ continue;
+ }
+
+ // Check wake pipe
+ if (FD_ISSET(monitor->wake_pipe[0], &readfds)) {
+ char buf[256];
+ read_pipe(monitor->wake_pipe[0], buf, sizeof(buf));
+ // Socket list changed, loop again to rebuild fd_sets
+ continue;
+ }
+
+ // Check sockets for events
+ monitor->mutex.lock();
+
+ for (auto& entry : monitor->socket_list) {
+ if (entry.s == INVALID_SOCKET) continue;
+
+ int events = 0;
+ // Add slight delay if we are spinning on Level Triggered events to prevent CPU hog
+ // Only if we found something? No, loop level.
+ // We rely on select() usage. If select returns immediately, we spin.
+ // We cannot easily throttle here without affecting response time.
+ // Hoping App clears the signal buffers or handles it.
+
+ if (FD_ISSET(entry.s, &readfds)) {
+ // Filter "phantom" read events (where select says ready but peek returns error/block)
+ if ((entry.eventmask & REP_READ) || (entry.eventmask & REP_CLOSE)) {
+ int peek = peek_socket(entry.s);
+ if (peek == 2) { // Data
+ if ((entry.eventmask & REP_READ) && !(entry.fired_mask & REP_READ)) {
+ events |= REP_READ;
+ }
+ } else if (peek == 1) { // EOF
+ if ((entry.eventmask & REP_CLOSE) && !(entry.fired_mask & REP_CLOSE)) {
+ events |= REP_CLOSE;
+ }
+ // EOF is also readable (read returns 0)
+ if ((entry.eventmask & REP_READ) && !(entry.fired_mask & REP_READ)) {
+ events |= REP_READ;
+ }
+ }
+ }
+ if ((entry.eventmask & REP_ACCEPT) && !(entry.fired_mask & REP_ACCEPT)) {
+ events |= REP_ACCEPT;
+ }
+ }
+
+ if (FD_ISSET(entry.s, &writefds)) {
+ bool wrote = false;
+
+ if (entry.connecting) {
+ int error = 0;
+ socklen_t len = sizeof(error);
+ if (getsockopt(entry.s, SOL_SOCKET, SO_ERROR, (char*)&error, &len) < 0 || error != 0) {
+ // Connection failed
+ write_log("BSDSOCK: Socket %d connect check failed (errno=%d), checking SO_ERROR\n", entry.sd, errno);
+ // We don't set REP_ERROR here, maybe we should? But WinUAE usually handles it via generic error?
+ // Actually, we should probably set REP_ERROR if the app asked for it.
+ if (entry.eventmask & REP_ERROR) events |= REP_ERROR;
+ entry.connecting = false;
+ } else {
+ entry.connecting = false;
+ entry.connected = true;
+
+ if ((entry.eventmask & REP_CONNECT) && !(entry.fired_mask & REP_CONNECT)) {
+ events |= REP_CONNECT;
+ }
+ // Writable now
+ if ((entry.eventmask & REP_WRITE) && !(entry.fired_mask & REP_WRITE)) {
+ events |= REP_WRITE;
+ }
+ // Do NOT set REP_READ here blindly. Let readfds handle it.
+ BSDTRACE((_T("BSDSOCK: Socket %d CONNECT completed successfully\n"), entry.sd));
+ }
+ wrote = true;
+ }
+
+ // Standard Write Signaling
+ if ((entry.eventmask & REP_WRITE) && !(entry.fired_mask & REP_WRITE)) {
+ events |= REP_WRITE;
+ wrote = true;
+ }
+
+ // Fallback: If App asked for REP_CONNECT but NOT REP_WRITE, and we are connected (writable).
+ // We must signal REP_CONNECT (or WRITE) to wake it up.
+ if (!wrote && (entry.eventmask & REP_CONNECT) && entry.connected) {
+ events |= REP_WRITE | REP_CONNECT;
+ }
+ }
+
+ if (FD_ISSET(entry.s, &exceptfds)) {
+ if (entry.eventmask & REP_OOB) {
+ events |= REP_OOB;
+ }
+ }
+
+ // Post events to Amiga and clear them from the mask (one-shot delivery)
+ if (events) {
+ post_socket_event(entry.sb, entry.sd, events);
+
+ // Mark these events as fired so we don't post them again until re-enabled
+ // This implements the one-shot behavior required by Amiga apps
+ entry.fired_mask |= events;
+
+ // Do NOT clear them from eventmask, as that loses the user's request.
+ // Do NOT update ftable here, post_socket_event handles the SET_ flags.
+
+ BSDTRACE((_T("BSDSOCK: Fired events 0x%x for socket %d, fired_mask now 0x%x\n"),
+ events, entry.sd, entry.fired_mask));
+ }
+ }
+
+ monitor->mutex.unlock();
+
+ // Throttle the loop to prevent lock starvation of the Amiga Task
+ // If we spin freely, addtosigqueue() locks can starve GetSocketEvents().
+ Sleep(10);
+ }
+
+ write_log("BSDSOCK: Event monitor thread exiting\n");
+ return;
+}
+
+// Start the event monitor thread
+static bool start_event_monitor()
+{
+ if (g_event_monitor) {
+ return true; // Already running
+ }
+
+ g_event_monitor = new event_monitor();
+ if (!g_event_monitor) {
+ write_log("BSDSOCK: Failed to allocate event monitor\n");
+ return false;
+ }
+
+ // Create wake pipe
+ if (pipe(g_event_monitor->wake_pipe) < 0) {
+ write_log("BSDSOCK: Failed to create wake pipe: %d\n", errno);
+ delete g_event_monitor;
+ g_event_monitor = nullptr;
+ return false;
+ }
+
+ // Initialize state
+ g_event_monitor->running = true;
+ g_event_monitor->socket_list.clear();
+
+ // Start thread
+ if (!uae_start_thread("bsdsock_event_monitor", event_monitor_thread, g_event_monitor, &g_event_monitor->thread)) {
+ write_log("BSDSOCK: Failed to start event monitor thread\n");
+ close_pipe(g_event_monitor->wake_pipe[0]);
+ close_pipe(g_event_monitor->wake_pipe[1]);
+ delete g_event_monitor;
+ g_event_monitor = nullptr;
+ return false;
+ }
+
+ write_log("BSDSOCK: Event monitor started\n");
+ return true;
+}
+
+// Stop the event monitor thread
+static void stop_event_monitor()
+{
+ if (!g_event_monitor) {
+ return;
+ }
+
+ write_log("BSDSOCK: Stopping event monitor\n");
+
+ // Signal thread to stop
+ g_event_monitor->running = false;
+
+ // Wake up the thread
+ char wake = 1;
+ write_pipe(g_event_monitor->wake_pipe[1], &wake, 1);
+
+ // Wait for thread to exit
+ uae_wait_thread(g_event_monitor->thread);
+
+ // Cleanup
+ close_pipe(g_event_monitor->wake_pipe[0]);
+ close_pipe(g_event_monitor->wake_pipe[1]);
+ delete g_event_monitor;
+ g_event_monitor = nullptr;
+
+ write_log("BSDSOCK: Event monitor stopped\n");
+}
+
+// Register a socket for event monitoring
+static void register_socket_events(struct socketbase* sb, int sd, SOCKET_TYPE s, int eventmask)
+{
+ if (!g_event_monitor) {
+ if (!start_event_monitor()) {
+ write_log("BSDSOCK: Failed to start event monitor for socket %d\n", sd);
+ return;
+ }
+ }
+
+ g_event_monitor->mutex.lock();
+
+ // Check if socket already registered
+ bool found = false;
+ for (auto& entry : g_event_monitor->socket_list) {
+ if (entry.sb == sb && entry.sd == sd) {
+ // Update existing entry
+ entry.eventmask = eventmask;
+ found = true;
+ BSDTRACE((_T("BSDSOCK: Updated event mask 0x%x for socket %d\n"), eventmask, sd));
+ break;
+ }
+ }
+
+ if (!found) {
+ // Add new entry
+ socket_event_entry entry;
+ entry.sb = sb;
+ entry.sd = sd;
+ entry.s = s;
+ entry.eventmask = eventmask;
+ entry.connecting = false;
+ // Determine actual connection state; bare sockets must not fire REP_WRITE/READ
+ struct sockaddr_in peer;
+ socklen_t plen = sizeof(peer);
+ entry.connected = (getpeername(s, (struct sockaddr*)&peer, &plen) == 0);
+ entry.fired_mask = 0;
+ g_event_monitor->socket_list.push_back(entry);
+
+ BSDTRACE((_T("BSDSOCK: Registered socket %d (native %d) for event monitoring (mask 0x%x)\n"), sd, s, eventmask));
+ }
+
+ // Wake up monitor thread to rebuild fd_sets
+ char wake = 1;
+ write_pipe(g_event_monitor->wake_pipe[1], &wake, 1);
+
+ g_event_monitor->mutex.unlock();
+}
+
+// Unregister a socket from event monitoring
+static void unregister_socket_events(struct socketbase* sb, int sd)
+{
+ if (!g_event_monitor) {
+ return;
+ }
+
+ g_event_monitor->mutex.lock();
+
+ // Remove socket from list
+ auto it = g_event_monitor->socket_list.begin();
+ while (it != g_event_monitor->socket_list.end()) {
+ if (it->sb == sb && it->sd == sd) {
+ it = g_event_monitor->socket_list.erase(it);
+ BSDTRACE((_T("BSDSOCK: Unregistered socket %d from event monitoring\n"), sd));
+ } else {
+ ++it;
+ }
+ }
+
+ // Wake up monitor thread
+ char wake = 1;
+ write_pipe(g_event_monitor->wake_pipe[1], &wake, 1);
+
+ g_event_monitor->mutex.unlock();
+}
+
+// Unregister all sockets for a socketbase (called during cleanup)
+static void unregister_all_socket_events(struct socketbase* sb)
+{
+ if (!g_event_monitor) {
+ return;
+ }
+
+ g_event_monitor->mutex.lock();
+
+ bool removed = false;
+ auto it = g_event_monitor->socket_list.begin();
+ while (it != g_event_monitor->socket_list.end()) {
+ if (it->sb == sb) {
+ it = g_event_monitor->socket_list.erase(it);
+ removed = true;
+ } else {
+ ++it;
+ }
+ }
+
+ if (removed) {
+ char wake = 1;
+ write_pipe(g_event_monitor->wake_pipe[1], &wake, 1);
+ }
+
+ g_event_monitor->mutex.unlock();
+}
+
+// Set the connecting state for a socket
+static void set_socket_connecting(struct socketbase* sb, int sd, bool connecting)
+{
+ if (!g_event_monitor) return;
+
+ g_event_monitor->mutex.lock();
+ for (auto& entry : g_event_monitor->socket_list) {
+ if (entry.sb == sb && entry.sd == sd) {
+ entry.connecting = connecting;
+ BSDTRACE((_T("BSDSOCK: Socket %d connecting state set to %d\n"), sd, connecting));
+ break;
+ }
+ }
+ // Wake up monitor to update handling
+ if (g_event_monitor->wake_pipe[1] != -1) {
+ char b = 1;
+ write_pipe(g_event_monitor->wake_pipe[1], &b, 1);
+ }
+ g_event_monitor->mutex.unlock();
+}
+
+// Re-enable specific events for a socket (called by IO functions)
+static void socket_reenable_events(struct socketbase* sb, int sd, int events)
+{
+ if (!g_event_monitor) return;
+
+ g_event_monitor->mutex.lock();
+ for (auto& entry : g_event_monitor->socket_list) {
+ if (entry.sb == sb && entry.sd == sd) {
+ if (entry.fired_mask & events) {
+ entry.fired_mask &= ~events;
+ BSDTRACE((_T("BSDSOCK: Re-enabled events 0x%x for socket %d\n"), events, sd));
+ // Wake up monitor to check this socket again
+ if (g_event_monitor->wake_pipe[1] != -1) {
+ char b = 1;
+ write_pipe(g_event_monitor->wake_pipe[1], &b, 1);
+ }
+ }
+ break;
+ }
+ }
+ g_event_monitor->mutex.unlock();
+}
+
+static int copysockaddr_a2n(struct sockaddr_in* addr, uae_u32 a_addr, unsigned int len)
+{
+ if ((len > sizeof(struct sockaddr_in)) || (len < 8))
+ return 1;
+
+ if (a_addr == 0)
+ return 0;
+
+ addr->sin_family = get_byte(a_addr + 1);
+#if defined(__HAIKU__)
+ if (addr->sin_family == 2) addr->sin_family = AF_INET;
+#endif
+ addr->sin_port = htons(get_word(a_addr + 2));
+ addr->sin_addr.s_addr = htonl(get_long(a_addr + 4));
+
+ if (len > 8)
+ memcpy(&addr->sin_zero, get_real_address(a_addr + 8), static_cast<size_t>(len) - 8); /* Pointless? */
+
+ return 0;
+}
+
+/*
+ * Copy a sockaddr object from native space to amiga space
+ */
+static int copysockaddr_n2a (uae_u32 a_addr, const struct sockaddr_in *addr, unsigned int len)
+{
+ if (len < 8)
+ return 1;
+
+ if (a_addr == 0)
+ return 0;
+
+ put_byte (a_addr, 0); /* Anyone use this field? */
+#if defined(__HAIKU__)
+ { int amiga_af = addr->sin_family;
+ if (amiga_af == AF_INET) amiga_af = 2;
+ put_byte(a_addr + 1, amiga_af); }
+#else
+ put_byte (a_addr + 1, addr->sin_family);
+#endif
+ put_word (a_addr + 2, ntohs (addr->sin_port));
+ put_long (a_addr + 4, ntohl (addr->sin_addr.s_addr));
+
+ if (len > 8)
+ memset (get_real_address (a_addr + 8), 0, static_cast<size_t>(len) - 8);
+
+ return 0;
+}
+
+/*
+ * Copy a hostent object from native space to amiga space
+ */
+static void copyHostent(TrapContext* ctx, const struct hostent* hostent, SB)
+{
+ int size = 28;
+ int i;
+ int numaddr = 0;
+ int numaliases = 0;
+ uae_u32 aptr;
+
+ if (hostent->h_name != NULL)
+ size += strlen(hostent->h_name) + 1;
+
+ if (hostent->h_aliases != NULL)
+ while (hostent->h_aliases[numaliases])
+ size += strlen(hostent->h_aliases[numaliases++]) + 5;
+
+ if (hostent->h_addr_list != NULL) {
+ while (hostent->h_addr_list[numaddr])
+ numaddr++;
+ size += numaddr * (hostent->h_length + 4);
+ }
+
+ aptr = sb->hostent + 28 + numaliases * 4 + numaddr * 4;
+
+ // transfer hostent to Amiga memory
+ trap_put_long(ctx, sb->hostent + 4, sb->hostent + 20);
+ trap_put_long(ctx, sb->hostent + 8, hostent->h_addrtype);
+ trap_put_long(ctx, sb->hostent + 12, hostent->h_length);
+ trap_put_long(ctx, sb->hostent + 16, sb->hostent + 24 + numaliases * 4);
+
+ for (i = 0; i < numaliases; i++)
+ trap_put_long(ctx, sb->hostent + 20 + i * 4, addstr(ctx, &aptr, hostent->h_aliases[i]));
+ trap_put_long(ctx, sb->hostent + 20 + numaliases * 4, 0);
+
+ for (i = 0; i < numaddr; i++) {
+ trap_put_long(ctx, sb->hostent + 24 + (numaliases + i) * 4, addmem(ctx, &aptr, hostent->h_addr_list[i], hostent->h_length));
+ }
+ trap_put_long(ctx, sb->hostent + 24 + numaliases * 4 + numaddr * 4, 0);
+ trap_put_long(ctx, sb->hostent, aptr);
+ addstr(ctx, &aptr, hostent->h_name);
+
+ bsdsocklib_seterrno(ctx, sb, 0);
+}
+
+/*
+ * Copy a protoent object from native space to Amiga space
+ */
+static void copyProtoent(TrapContext* ctx, SB, const struct protoent* p)
+{
+ int size = 16;
+ int numaliases = 0;
+ int i;
+ uae_u32 aptr;
+
+ // compute total size of protoent
+ if (p->p_name != NULL)
+ size += strlen(p->p_name) + 1;
+
+ if (p->p_aliases != NULL)
+ while (p->p_aliases[numaliases])
+ size += strlen(p->p_aliases[numaliases++]) + 5;
+
+ if (sb->protoent) {
+ uae_FreeMem(ctx, sb->protoent, sb->protoentsize, sb->sysbase);
+ }
+
+ sb->protoent = uae_AllocMem(ctx, size, 0, sb->sysbase);
+
+ if (!sb->protoent) {
+ write_log("BSDSOCK: WARNING - copyProtoent() ran out of Amiga memory (couldn't allocate %d bytes)\n", size);
+ bsdsocklib_seterrno(ctx, sb, 12); // ENOMEM
+ return;
+ }
+
+ sb->protoentsize = size;
+
+ aptr = sb->protoent + 16 + numaliases * 4;
+
+ // transfer protoent to Amiga memory
+ trap_put_long(ctx, sb->protoent + 4, sb->protoent + 12);
+ trap_put_long(ctx, sb->protoent + 8, p->p_proto);
+
+ for (i = 0; i < numaliases; i++)
+ trap_put_long(ctx, sb->protoent + 12 + i * 4, addstr(ctx, &aptr, p->p_aliases[i]));
+ trap_put_long(ctx, sb->protoent + 12 + numaliases * 4, 0);
+ trap_put_long(ctx, sb->protoent, aptr);
+ addstr(ctx, &aptr, p->p_name);
+ bsdsocklib_seterrno(ctx, sb, 0);
+}
+
+uae_u32 bsdthr_Accept_2 (SB)
+{
+ int foo, s, s2;
+ long flags;
+ struct sockaddr_in addr{};
+ socklen_t hlen = sizeof (struct sockaddr_in);
+
+ if ((s = accept (sb->s, (struct sockaddr *)&addr, &hlen)) >= 0) {
+ if ((flags = fcntl (s, F_GETFL)) == -1)
+ flags = 0;
+ fcntl (s, F_SETFL, flags & ~O_NONBLOCK); /* @@@ Don't do this if it's supposed to stay nonblocking... */
+ s2 = getsd (sb->context, sb, s);
+ if (s2 == -1) {
+ write_log("bsdthr_Accept_2: descriptor table full, closing accepted socket %d\n", s);
+ close(s);
+ return -1;
+ }
+ sb->ftable[s2-1] = sb->ftable[sb->len]; /* new socket inherits the old socket's properties */
+ write_log ("Accept: AmigaSide %d, NativeSide %d, len %d(%d)", sb->resultval, s, hlen, get_long (sb->a_addrlen));
+ printSockAddr (&addr);
+ foo = get_long (sb->a_addrlen);
+ if (foo > 16)
+ put_long (sb->a_addrlen, 16);
+ copysockaddr_n2a (sb->a_addr, &addr, foo);
+ return s2 - 1;
+ } else {
+ return -1;
+ }
+}
+
+uae_u32 bsdthr_Recv_2 (SB)
+{
+ int foo;
+ int socktype = 0;
+ socklen_t optlen = sizeof(socktype);
+ getsockopt(sb->s, SOL_SOCKET, SO_TYPE, (char*)&socktype, &optlen);
+ int retries = (socktype == SOCK_RAW) ? 5 : 1;
+ if (sb->from == 0) {
+ ssize_t n;
+ do {
+ n = recv(sb->s, (char*)sb->buf, sb->len, sb->flags /*| MSG_NOSIGNAL*/);
+ foo = (int)n;
+ if (foo >= 0) break;
+ } while (errno == EINTR && --retries > 0);
+ } else {
+ struct sockaddr_in addr{};
+ socklen_t l = sizeof(struct sockaddr_in);
+ int i = get_long(sb->fromlen);
+ ssize_t n;
+ copysockaddr_a2n(&addr, sb->from, i);
+ do {
+ n = recvfrom(sb->s, (char*)sb->buf, sb->len, sb->flags | MSG_NOSIGNAL, (struct sockaddr *)&addr, &l);
+ foo = (int)n;
+ if (foo >= 0) {
+ copysockaddr_n2a(sb->from, &addr, l);
+ put_long(sb->fromlen, l);
+ break;
+ }
+ } while (errno == EINTR && --retries > 0);
+ }
+ return foo;
+}
+
+uae_u32 bsdthr_Send_2 (SB)
+{
+ if (sb->to == 0) {
+ ssize_t n;
+ n = send (sb->s, (const char*)sb->buf, sb->len, sb->flags | MSG_NOSIGNAL);
+ return (int)n;
+ } else {
+ struct sockaddr_in addr{};
+ int l = sizeof (struct sockaddr_in);
+ ssize_t n;
+ copysockaddr_a2n (&addr, sb->to, sb->tolen);
+ n = sendto (sb->s, (const char*)sb->buf, sb->len, sb->flags | MSG_NOSIGNAL, (struct sockaddr *)&addr, l);
+ return (int)n;
+ }
+}
+
+uae_u32 bsdthr_Connect_2 (SB)
+{
+ if (sb->action == 1) {
+ struct sockaddr_in addr{};
+ int len = sizeof (struct sockaddr_in);
+ int retval;
+ copysockaddr_a2n (&addr, sb->a_addr, sb->a_addrlen);
+ retval = connect (sb->s, (struct sockaddr *)&addr, len);
+ /* Hack: I need to set the action to something other than
+ * 1 but I know action == 2 does the correct thing
+ */
+ sb->action = 2;
+ if (retval == 0) {
+ errno = 0;
+ }
+ return retval;
+ } else {
+ int foo;
+ socklen_t bar;
+ bar = sizeof (foo);
+ if (getsockopt (sb->s, SOL_SOCKET, SO_ERROR, (char*)&foo, &bar) == 0) {
+ errno = foo;
+ return (foo == 0) ? 0 : -1;
+ }
+ return -1;
+ }
+}
+
+uae_u32 bsdthr_SendRecvAcceptConnect (uae_u32 (*tryfunc)(SB), SB)
+{
+ return bsdthr_blockingstuff (tryfunc, sb);
+}
+
+uae_u32 bsdthr_blockingstuff(uae_u32(*tryfunc)(SB), SB)
+{
+ int done = 0, foo = 0;
+ long flags;
+ int nonblock;
+ int saved_errno = 0;
+ int socktype = 0;
+ socklen_t optlen = sizeof(socktype);
+ int is_raw = 0;
+ struct timeval orig_timeout = {0}, timeout = {0};
+ socklen_t tvlen = sizeof(orig_timeout);
+ int timeout_set = 0;
+ if ((flags = fcntl(sb->s, F_GETFL)) == -1)
+ flags = 0;
+ // Check if this is a raw socket
+ if (getsockopt(sb->s, SOL_SOCKET, SO_TYPE, (char*)&socktype, &optlen) == 0 && socktype == SOCK_RAW) {
+ is_raw = 1;
+ // Save original timeout
+ if (getsockopt(sb->s, SOL_SOCKET, SO_RCVTIMEO, (char*)&orig_timeout, &tvlen) == 0) {
+ timeout_set = 1;
+ }
+ // Set a 1 second timeout for raw sockets
+ timeout.tv_sec = 1;
+ timeout.tv_usec = 0;
+ setsockopt(sb->s, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout));
+ }
+ nonblock = (flags & O_NONBLOCK);
+ // Only set non-blocking for non-raw sockets
+ if (!is_raw) {
+ fcntl(sb->s, F_SETFL, flags | O_NONBLOCK);
+ }
+ while (!done) {
+ done = 1;
+ do {
+ foo = tryfunc(sb);
+ } while (foo < 0 && errno == EINTR); // retry on EINTR
+ /* Save errno immediately after tryfunc(); any intervening call (write_log,
+ * getsockopt, etc.) can clobber it. Use saved_errno for all checks below,
+ * and restore it so code inside the block that reads errno directly is consistent. */
+ saved_errno = errno;
+ if (foo < 0 && !nonblock) {
+ errno = saved_errno;
+ if ((saved_errno == EAGAIN) || (saved_errno == EWOULDBLOCK) || (saved_errno == EINPROGRESS)) {
+ fd_set readset, writeset, exceptset;
+ int maxfd = (sb->s > sb->sockabort[0]) ? sb->s : sb->sockabort[0];
+ int num;
+
+ FD_ZERO(&readset);
+ FD_ZERO(&writeset);
+ FD_ZERO(&exceptset);
+
+ if (sb->action == 3 || sb->action == 6)
+ FD_SET(sb->s, &readset);
+ if (sb->action == 2 || sb->action == 1 || sb->action == 4)
+ FD_SET(sb->s, &writeset);
+ FD_SET(sb->sockabort[0], &readset);
+
+ do {
+ num = select(maxfd + 1, &readset, &writeset, &exceptset, NULL);
+ } while (num == -1 && errno == EINTR); // retry on EINTR
+ if (num == -1) {
+ int _select_err = errno; /* save before write_log/fcntl/setsockopt clobber it */
+ write_log("Blocking select(%d) returns -1,errno is %d\n", sb->sockabort[0], _select_err);
+ if (!is_raw) fcntl(sb->s, F_SETFL, flags);
+ if (is_raw && timeout_set) setsockopt(sb->s, SOL_SOCKET, SO_RCVTIMEO, (const char*)&orig_timeout, sizeof(orig_timeout));
+ errno = _select_err; /* restore after cleanup calls */
+ return -1;
+ }
+
+ if (FD_ISSET(sb->sockabort[0], &readset) || FD_ISSET(sb->sockabort[0], &writeset)) {
+ /* reset sock abort pipe */
+ /* read from the pipe to reset it */
+ clearsockabort(sb);
+ errno = EINTR;
+ done = 1;
+ }
+ else {
+ done = 0;
+ }
+ }
+ else if (errno == EINTR)
+ done = 1;
+ }
+ }
+ if (!is_raw) fcntl(sb->s, F_SETFL, flags);
+ if (is_raw && timeout_set) setsockopt(sb->s, SOL_SOCKET, SO_RCVTIMEO, (const char*)&orig_timeout, sizeof(orig_timeout));
+ /* Restore errno after fcntl/setsockopt cleanup; caller (bsdlib_threadfunc) reads
+ * errno via SETERRNO immediately after we return. */
+ errno = saved_errno;
+ return foo;
+}
+
+static void bsdlib_threadfunc(void* arg)
+{
+ auto* sb = (struct socketbase*)arg;
+
+ while (1) {
+ uae_sem_wait(&sb->sem);
+
+ TrapContext* ctx = sb->context;
+
+ switch (sb->action) {
+ case 0: /* kill thread (CloseLibrary) */
+
+ uae_sem_destroy(&sb->sem);
+ return;
+
+ case 1: /* Connect */
+ sb->resultval = bsdthr_SendRecvAcceptConnect(bsdthr_Connect_2, sb);
+ if ((int)sb->resultval < 0) {
+ SETERRNO;
+ } else {
+ bsdsocklib_seterrno(ctx, sb, 0);
+ }
+ break;
+
+ /* @@@ Should check (from|to)len so it's 16.. */
+ case 2: /* Send[to] */
+ sb->resultval = bsdthr_SendRecvAcceptConnect(bsdthr_Send_2, sb);
+ if ((int)sb->resultval < 0) {
+ SETERRNO;
+ } else {
+ bsdsocklib_seterrno(ctx, sb, 0);
+ }
+ break;
+
+ case 3: /* Recv[from] */
+ sb->resultval = bsdthr_SendRecvAcceptConnect(bsdthr_Recv_2, sb);
+ if ((int)sb->resultval < 0) {
+ SETERRNO;
+ } else {
+ bsdsocklib_seterrno(ctx, sb, 0);
+ }
+ break;
+
+ case 4: { /* Gethostbyname */
+#if defined(__linux__)
+ struct hostent hent, *tmphostent = nullptr;
+ char buf[1024];
+ int herr, ret;
+ ret = gethostbyname_r((char*)get_real_address(sb->name), &hent, buf, sizeof(buf), &tmphostent, &herr);
+ if (ret == 0 && tmphostent) {
+ copyHostent(ctx, tmphostent, sb);
+ bsdsocklib_setherrno(ctx, sb, 0);
+ } else {
+ bsdsocklib_setherrno(ctx, sb, herr);
+ SETERRNO;
+ }
+#else
+ std::lock_guard<std::mutex> lock(bsdsock_mutex);
+ struct hostent* tmphostent = gethostbyname((char*)get_real_address(sb->name));
+ if (tmphostent) {
+ copyHostent(ctx, tmphostent, sb);
+ bsdsocklib_setherrno(ctx, sb, 0);
+ } else {
+ SETHERRNO;
+ SETERRNO;
+ }
+#endif
+ break;
+ }
+
+ case 5: /* WaitSelect */
+ sb->resultval = bsdthr_WaitSelect(sb);
+ break;
+
+ case 6: /* Accept */
+ sb->resultval = bsdthr_SendRecvAcceptConnect(bsdthr_Accept_2, sb);
+ if ((int)sb->resultval < 0) {
+ SETERRNO;
+ }
+ break;
+
+ case 7: {
+#if defined(__linux__)
+ struct hostent hent, *tmphostent = nullptr;
+ char buf[1024];
+ int herr, ret;
+ ret = gethostbyaddr_r((char*)get_real_address(sb->name), sb->a_addrlen, sb->flags, &hent, buf, sizeof(buf), &tmphostent, &herr);
+ if (ret == 0 && tmphostent) {
+ copyHostent(ctx, tmphostent, sb);
+ bsdsocklib_setherrno(ctx, sb, 0);
+ } else {
+ bsdsocklib_setherrno(ctx, sb, herr);
+ SETERRNO;
+ }
+#else
+ std::lock_guard<std::mutex> lock(bsdsock_mutex);
+ struct hostent* tmphostent = gethostbyaddr((const char*)get_real_address(sb->name), sb->a_addrlen, sb->flags);
+ if (tmphostent) {
+ copyHostent(ctx, tmphostent, sb);
+ bsdsocklib_setherrno(ctx, sb, 0);
+ } else {
+ SETHERRNO;
+ SETERRNO;
+ }
+#endif
+ break;
+ }
+ }
+ SETSIGNAL;
+ }
+ return;
+}
+
+void clearsockabort(SB)
+{
+ int chr;
+
+ while (read_pipe(sb->sockabort[0], &chr, sizeof(chr)) >= 0) {
+ }
+}
+
+int init_socket_layer(void)
+{
+ int result = 0;
+
+ if (currprefs.socket_emu) {
+ uae_sem_init(&sem_queue, 0, 1);
+ return 1;
+ }
+
+ return result;
+}
+
+void deinit_socket_layer(void)
+{
+ stop_event_monitor();
+ uae_sem_destroy(&sem_queue);
+}
+
+void locksigqueue(void)
+{
+ uae_sem_wait(&sem_queue);
+}
+
+void unlocksigqueue(void)
+{
+ uae_sem_post(&sem_queue);
+}
+
+int host_sbinit (TrapContext *ctx, SB)
+{
+ if (pipe (sb->sockabort) < 0) {
+ return 0;
+ }
+
+ if (fcntl (sb->sockabort[0], F_SETFL, O_NONBLOCK) < 0) {
+ write_log ("Set nonblock failed %d\n", errno);
+ }
+
+ uae_sem_init (&sb->sem, 0, 0);
+
+ /* Alloc hostent buffer */
+ sb->hostent = uae_AllocMem (ctx, 1024, 0, sb->sysbase);
+ sb->hostentsize = 1024;
+
+ /* @@@ The thread should be PTHREAD_CREATE_DETACHED */
+ if (uae_start_thread ("bsdsocket", bsdlib_threadfunc, (void *)sb, &sb->thread) == BAD_THREAD) {
+ write_log ("BSDSOCK: Failed to create thread.\n");
+ uae_sem_destroy (&sb->sem);
+ close_pipe (sb->sockabort[0]);
+ close_pipe (sb->sockabort[1]);
+ return 0;
+ }
+ return 1;
+}
+
+void host_closesocketquick (int s)
+{
+ struct linger l{};
+ l.l_onoff = 0;
+ l.l_linger = 0;
+ if(s != -1) {
+ setsockopt (s, SOL_SOCKET, SO_LINGER, (const char*)&l, sizeof(l));
+ close_socket (s);
+ }
+}
+
+void host_sbcleanup (SB)
+{
+ int i;
+
+ if (!sb) {
+ return;
+ }
+
+ unregister_all_socket_events(sb);
+
+ uae_thread_id thread = sb->thread;
+ /* Abort any pending blocking operation BEFORE closing the pipe.
+ * Without this, a connect() blocked in select() inside bsdthr_blockingstuff
+ * will never see the wakeup and the thread hangs forever. */
+ sb->action = 0;
+ sockabort(sb); /* unblocks any select() waiting on sockabort[0] */
+ uae_sem_post(&sb->sem); /* wakes thread if blocked on semaphore instead */
+
+ close_pipe (sb->sockabort[0]);
+ close_pipe (sb->sockabort[1]);
+ for (i = 0; i < sb->dtablesize; i++) {
+ if (sb->dtable[i] != -1) {
+ close_socket(sb->dtable[i]);
+ }
+ }
+
+ /* We need to join with the socket thread to allow the thread to die
+ * and clean up resources when the underlying thread layer is pthreads.
+ * Ideally, this shouldn't be necessary, but, for example, when SDL uses
+ * pthreads, it always creates joinable threads - and we can't do anything
+ * about that. */
+ uae_wait_thread (thread);
+}
+
+void host_sbreset (void)
+{
+ stop_event_monitor();
+}
+
+void sockabort (SB)
+{
+ int chr = 1;
+ if (write_pipe (sb->sockabort[1], &chr, sizeof (chr)) != sizeof (chr)) {
+ write_log("sockabort - did not write %zd bytes\n", sizeof(chr));
+ }
+}
+
+int host_dup2socket(TrapContext *ctx, SB, int fd1, int fd2)
+{
+ int s1, s2;
+
+ fd1++;
+
+ s1 = getsock(ctx, sb, fd1);
+ if (s1 != -1) {
+ if (fd2 != -1) {
+ if ((unsigned int) (fd2) >= (unsigned int) sb->dtablesize) {
+ bsdsocklib_seterrno (ctx, sb, 9); /* EBADF */
+ return -1;
+ }
+ fd2++;
+ s2 = getsock(ctx, sb, fd2);
+ if (s2 != -1) {
+ unregister_socket_events(sb, fd2 - 1);
+ close_socket (s2);
+ }
+ setsd (ctx, sb, fd2, dup (s1));
+ return fd2 - 1;
+ } else {
+ fd2 = getsd (ctx, sb, 1);
+ if (fd2 != -1) {
+ setsd (ctx, sb, fd2, dup (s1));
+ return (fd2 - 1);
+ } else {
+ return -1;
+ }
+ }
+ }
+ return -1;
+}
+
+int host_socket(TrapContext *ctx, SB, int af, int type, int protocol)
+{
+ int sd;
+ int s;
+#if defined(__HAIKU__)
+ /* On Haiku AF_INET=1, but Amiga/BSD use AF_INET=2. Translate. */
+ if (af == 2) af = AF_INET;
+#endif
+
+ write_log("socket(%s,%s,%d) -> ",af == AF_INET ? "AF_INET" : "AF_other",
+ type == SOCK_STREAM ? "SOCK_STREAM" : type == SOCK_DGRAM ?
+ "SOCK_DGRAM " : type == SOCK_RAW ? "SOCK_RAW" : "SOCK_other", protocol);
+
+ if ((s = socket (af, type, protocol)) == -1) {
+ SETERRNO;
+ write_log("failed (%d)\n", sb->sb_errno);
+ return -1;
+ } else {
+ int arg = 1;
+ sd = getsd (ctx, sb, s);
+ if (sd == -1) {
+ write_log("host_socket: descriptor table full, closing socket %d\n", s);
+ close(s);
+ return -1;
+ }
+ setsockopt (s, SOL_SOCKET, SO_REUSEADDR, (const char*)&arg, sizeof(arg));
+ }
+
+ sb->ftable[sd-1] = SF_BLOCKING;
+ write_log("socket returns Amiga %d, NativeSide %d\n", sd - 1, s);
+ return sd - 1;
+}
+
+uae_u32 host_bind(TrapContext *ctx, SB, uae_u32 sd, uae_u32 name, uae_u32 namelen)
+{
+ uae_u32 success = 0;
+ struct sockaddr_in addr{};
+ int len = sizeof (struct sockaddr_in);
+ int s;
+
+ s = getsock(ctx, sb, sd + 1);
+ if (s == -1) {
+ sb->resultval = -1;
+ bsdsocklib_seterrno (ctx, sb, 9); /* EBADF */
+ return -1;
+ }
+
+ write_log("bind(%u[%d], 0x%x, %u) -> ", sd, s, name, namelen);
+ copysockaddr_a2n (&addr, name, namelen);
+ printSockAddr (&addr);
+ if ((success = ::bind (s, (struct sockaddr *)&addr, len)) != (uae_u32)0) {
+ SETERRNO;
+ // Improved error logging
+ write_log("failed (%d: %s)\n", sb->sb_errno, strerror(errno));
+ // Special message for privileged ports
+ if (errno == EACCES && ntohs(addr.sin_port) < 1024) {
+ write_log("bind() failed: Port %d is privileged (<1024), requires root privileges.\n", ntohs(addr.sin_port));
+ }
+ } else {
+ write_log("OK\n");
+ }
+ return success;
+}
+
+uae_u32 host_listen(TrapContext *ctx, SB, uae_u32 sd, uae_u32 backlog)
+{
+ int s;
+ uae_u32 success = -1;
+
+ write_log("listen(%d,%d) -> ", sd, backlog);
+ s = getsock(ctx, sb, sd + 1);
+
+ if (s == -1) {
+ bsdsocklib_seterrno (ctx, sb, 9);
+ return -1;
+ }
+
+ if ((success = listen (s, backlog)) != 0) {
+ SETERRNO;
+ write_log("failed (%d)\n", sb->sb_errno);
+ } else {
+ write_log("OK\n");
+ }
+ return success;
+}
+
+void host_accept(TrapContext *ctx, SB, uae_u32 sd, uae_u32 name, uae_u32 namelen)
+{
+ sb->s = getsock(ctx, sb, sd + 1);
+ if (sb->s == -1) {
+ sb->resultval = -1;
+ bsdsocklib_seterrno (ctx, sb, 9); /* EBADF */
+ return;
+ }
+
+ write_log("accept(%d, %x, %x)\n", sb->s, name, namelen);
+ sb->a_addr = name;
+ sb->a_addrlen = namelen;
+ sb->action = 6;
+ sb->len = sd;
+ // used by bsdthr_Accept_2
+ sb->context = ctx;
+
+ uae_sem_post (&sb->sem);
+
+ WAITSIGNAL;
+ write_log("Accept returns %d\n", sb->resultval);
+
+ // Implicitly re-enable REP_ACCEPT
+ if (sb->resultval >= 0) {
+ socket_reenable_events(sb, sd, REP_ACCEPT);
+ }
+}
+
+void host_connect(TrapContext *ctx, SB, uae_u32 sd, uae_u32 name, uae_u32 namelen)
+{
+ SOCKET s;
+ static int wscounter;
+ int wscnt;
+
+ sd++;
+ wscnt = ++wscounter;
+
+ if (!addr_valid (_T("host_connect"), name, namelen))
+ return;
+
+ s = getsock(ctx, sb, sd);
+
+ if (s != INVALID_SOCKET) {
+ if (namelen <= MAXADDRLEN) {
+ sb->s = getsock(ctx, sb, sd);
+ sb->a_addr = name;
+ sb->a_addrlen = namelen;
+ sb->action = 1;
+
+ // Notify event monitor that connect is in progress
+ // Note: sd was incremented at start of function, so we must pass sd-1
+ // to match the 0-based descriptor used in registration
+ set_socket_connecting(sb, sd - 1, true);
+
+ uae_sem_post (&sb->sem);
+
+ WAITSIGNAL;
+
+ // Implicitly re-enable REP_CONNECT (and REP_WRITE as they are related on success)
+ socket_reenable_events(sb, sd - 1, REP_CONNECT | REP_WRITE);
+ } else {
+ write_log (_T("BSDSOCK: WARNING - Excessive namelen (%d) in connect():%d!\n"), namelen, wscnt);
+ }
+ } else {
+ sb->resultval = -1;
+ bsdsocklib_seterrno (ctx, sb, 9); /* EBADF */
+ return;
+ }
+}
+
+void host_sendto (TrapContext *ctx, SB, uae_u32 sd, uae_u32 msg, uae_u8 *hmsg, uae_u32 len, uae_u32 flags, uae_u32 to, uae_u32 tolen)
+{
+ SOCKET s;
+ char *realpt;
+ static int wscounter;
+ int wscnt;
+
+ wscnt = ++wscounter;
+
+ sd++;
+ s = getsock(ctx, sb, sd);
+
+ if (s != INVALID_SOCKET) {
+ if (hmsg == NULL) {
+ if (!addr_valid (_T("host_sendto1"), msg, 4))
+ return;
+ realpt = (char*)get_real_address (msg);
+ } else {
+ realpt = (char*)hmsg;
+ }
+
+ sb->s = s;
+ sb->buf = realpt;
+ sb->len = len;
+ sb->flags = flags;
+ sb->to = to;
+ sb->tolen = tolen;
+ sb->action = 2;
+
+ uae_sem_post (&sb->sem);
+
+ WAITSIGNAL;
+
+ // Implicitly re-enable REP_WRITE
+ socket_reenable_events(sb, sd - 1, REP_WRITE);
+
+ } else {
+ sb->resultval = -1;
+ bsdsocklib_seterrno (ctx, sb, 9); /* EBADF */
+ return;
+ }
+}
+
+void host_recvfrom(TrapContext *ctx, SB, uae_u32 sd, uae_u32 msg, uae_u8 *hmsg, uae_u32 len, uae_u32 flags, uae_u32 addr, uae_u32 addrlen)
+{
+ int s;
+ uae_char *realpt;
+ static int wscounter;
+ int wscnt;
+
+ wscnt = ++wscounter;
+
+ s = getsock(ctx, sb, sd + 1);
+
+ if (s != -1) {
+ if (hmsg == NULL) {
+ if (!addr_valid (_T("host_recvfrom1"), msg, 4))
+ return;
+ realpt = (char*)get_real_address (msg);
+ } else {
+ realpt = (char*)hmsg;
+ }
+ } else {
+ sb->resultval = -1;
+ bsdsocklib_seterrno (ctx, sb, 9); /* EBADF */;
+ return;
+ }
+
+ sb->s = s;
+ sb->buf = realpt;
+ sb->len = len;
+ sb->flags = flags;
+ sb->from = addr;
+ sb->fromlen= addrlen;
+ sb->action = 3;
+
+ uae_sem_post (&sb->sem);
+
+ WAITSIGNAL;
+
+ // Implicitly re-enable REP_READ and REP_OOB
+ socket_reenable_events(sb, sd, REP_READ | REP_OOB);
+}
+
+uae_u32 host_shutdown(SB, uae_u32 sd, uae_u32 how)
+{
+ TrapContext *ctx = NULL;
+ SOCKET s;
+
+ write_log("shutdown(%d,%d) -> ", sd, how);
+ s = getsock(ctx, sb, sd + 1);
+
+ if (s != INVALID_SOCKET) {
+ if (shutdown (s, how)) {
+ SETERRNO;
+ write_log("failed (%d)\n", sb->sb_errno);
+ } else {
+ write_log("OK\n");
+ return 0;
+ }
+ }
+
+ return -1;
+}
+
+void host_setsockopt(SB, uae_u32 sd, uae_u32 level, uae_u32 optname, uae_u32 optval, uae_u32 len)
+{
+ TrapContext* ctx = NULL;
+ int s = getsock(ctx, sb, sd + 1);
+ void* buf = NULL;
+ struct linger sl;
+ struct timeval timeout;
+
+ if (s == INVALID_SOCKET) {
+ sb->resultval = -1;
+ bsdsocklib_seterrno(ctx, sb, 9); /* EBADF */
+ return;
+ }
+
+ // Handle SO_EVENTMASK (0x2001) - Amiga-specific async event notification
+ // Must be checked BEFORE mapsockoptname validation since it's not in the mapping table
+ // Level 0xFFFF is Amiga's SOL_SOCKET value
+ if (level == 0xFFFF && optname == 0x2001) {
+ uae_u32 eventflags = 0;
+ if (optval && len >= 4) {
+ eventflags = get_long(optval);
+ }
+
+ // Fix for dynAMIte and other apps that rely on implicit Writability after Connect:
+ if (eventflags & REP_CONNECT) {
+ eventflags |= REP_WRITE;
+ write_log("BSDSOCK: Force-enabled REP_WRITE for socket %d (requested mask 0x%x -> 0x%x)\n", sd, get_long(optval), eventflags);
+ }
+
+ BSDTRACE((_T("BSDSOCK: SO_EVENTMASK called for socket %d, eventflags=0x%x\n"), sd, eventflags));
+
+ // Store event mask in ftable (using lower bits)
+ sb->ftable[sd] = (sb->ftable[sd] & ~REP_ALL) | (eventflags & REP_ALL);
+
+ // Register or unregister with event monitor
+ if (eventflags & REP_ALL) {
+ // Register socket for event monitoring
+ register_socket_events(sb, sd, s, eventflags & REP_ALL);
+ } else {
+ // Unregister socket from event monitoring
+ unregister_socket_events(sb, sd);
+ // Clear pending SET_* flags to prevent stale events on fd reuse
+ sb->ftable[sd] &= ~SET_ALL;
+ }
+
+ sb->resultval = 0;
+ return;
+ }
+
+ // Now map the level and option name
+ int nativelevel = mapsockoptlevel(level);
+ int nativeoptname = mapsockoptname(nativelevel, optname);
+
+ // Prevent invalid setsockopt calls
+ if (nativeoptname == -1) {
+ write_log("host_setsockopt: Invalid option 0x%x for level %d (native level %d), not calling setsockopt.\n", optname, level, nativelevel);
+ sb->resultval = -1;
+ errno = EINVAL;
+ SETERRNO;
+ return;
+ }
+
+ if (optval) {
+ buf = malloc(len);
+ if (buf == NULL) {
+ sb->resultval = -1;
+ bsdsocklib_seterrno(ctx, sb, 12); // ENOMEM
+ return;
+ }
+ if (nativeoptname == SO_LINGER) {
+ sl.l_onoff = get_long(optval);
+ sl.l_linger = get_long(optval + 4);
+ }
+ else if (nativeoptname == SO_RCVTIMEO || nativeoptname == SO_SNDTIMEO) {
+ timeout.tv_sec = get_long(optval);
+ timeout.tv_usec = get_long(optval + 4);
+ }
+ else {
+ mapsockoptvalue(nativelevel, nativeoptname, optval, buf);
+ }
+ }
+ if (nativeoptname == SO_RCVTIMEO || nativeoptname == SO_SNDTIMEO) {
+ sb->resultval = setsockopt(s, nativelevel, nativeoptname, (const char*)&timeout, sizeof(timeout));
+ }
+ else if (nativeoptname == SO_LINGER) {
+ sb->resultval = setsockopt(s, nativelevel, nativeoptname, (const char*)&sl, sizeof(sl));
+ }
+ else {
+ sb->resultval = setsockopt(s, nativelevel, nativeoptname, (const char*)buf, len);
+ }
+ if (buf)
+ free(buf);
+ SETERRNO;
+
+ write_log("setsockopt: sock %d, level %d, 'name' %d(%d), len %d -> %d, %d\n",
+ s, level, optname, nativeoptname, len,
+ sb->resultval, errno);
+}
+
+uae_u32 host_getsockopt(TrapContext* ctx, SB, uae_u32 sd, uae_u32 level, uae_u32 optname, uae_u32 optval, uae_u32 optlen)
+{
+ socklen_t len = 0;
+ int r;
+ int s;
+ int nativelevel = mapsockoptlevel(level);
+ int nativeoptname = mapsockoptname(nativelevel, optname);
+ void* buf = NULL;
+ struct linger sl;
+ struct timeval timeout;
+
+ s = getsock(ctx, sb, sd + 1);
+
+ if (s == INVALID_SOCKET) {
+ bsdsocklib_seterrno(ctx, sb, 9); /* EBADF */
+ return -1;
+ }
+
+ // Handle SO_EVENTMASK (0x2001) - Amiga-specific, no host equivalent
+ if (level == 0xFFFF && optname == 0x2001) {
+ if (optval && optlen) {
+ int mask = sb->ftable[sd] & REP_ALL;
+ put_long(optval, mask);
+ put_long(optlen, 4);
+ }
+ bsdsocklib_seterrno(ctx, sb, 0);
+ sb->resultval = 0;
+ return 0;
+ }
+
+ if (optlen) {
+ len = get_long(optlen);
+ buf = malloc(len);
+ if (buf == NULL) {
+ return -1;
+ }
+ }
+
+ if (nativeoptname == SO_RCVTIMEO || nativeoptname == SO_SNDTIMEO) {
+ len = sizeof(timeout);
+ r = getsockopt(s, nativelevel, nativeoptname, (char*)&timeout, &len);
+ }
+ else if (nativeoptname == SO_LINGER) {
+ len = sizeof(sl);
+ r = getsockopt(s, nativelevel, nativeoptname, (char*)&sl, &len);
+ }
+ else {
+ r = getsockopt(s, nativelevel, nativeoptname, optval ? (char*)buf : NULL, optlen ? &len : NULL);
+ }
+
+ // Write back Amiga-appropriate optlen for size-mismatched types
+ if (r == 0 && optlen) {
+ if (nativeoptname == SO_RCVTIMEO || nativeoptname == SO_SNDTIMEO) {
+ len = 8; // Amiga sizeof(struct timeval) = 4+4
+ } else if (nativeoptname == SO_LINGER) {
+ len = 8; // Amiga sizeof(struct linger) = 4+4
+ }
+ }
+
+ if (optlen)
+ put_long(optlen, len);
+
+ SETERRNO;
+ write_log("getsockopt: sock AmigaSide %d NativeSide %d, level %d, 'name' %x(%d), len %d -> %d, %d\n",
+ sd, s, level, optname, nativeoptname, len, r, errno);
+
+ if (optval) {
+ if (r == 0) {
+ if (nativeoptname == SO_RCVTIMEO || nativeoptname == SO_SNDTIMEO) {
+ put_long(optval, timeout.tv_sec);
+ put_long(optval + 4, timeout.tv_usec);
+ }
+ else if (nativeoptname == SO_LINGER) {
+ put_long(optval, sl.l_onoff);
+ put_long(optval + 4, sl.l_linger);
+ }
+ else {
+ mapsockoptreturn(nativelevel, nativeoptname, optval, buf);
+ }
+ }
+ }
+
+ if (buf != NULL)
+ free(buf);
+ return r;
+}
+
+uae_u32 host_getsockname(TrapContext *ctx, SB, uae_u32 sd, uae_u32 name, uae_u32 namelen)
+{
+ int s;
+ socklen_t len = sizeof (struct sockaddr_in);
+ struct sockaddr_in addr{};
+
+ write_log("getsockname(%u, 0x%x, %u) -> ", sd, name, len);
+
+ s = getsock(ctx, sb, sd + 1);
+
+ if (s != INVALID_SOCKET) {
+ if (getsockname (s, (struct sockaddr *)&addr, &len)) {
+ SETERRNO;
+ write_log("failed (%d)\n", sb->sb_errno);
+ } else {
+ int a_nl;
+ write_log("okay\n");
+ a_nl = get_long (namelen);
+ copysockaddr_n2a (name, &addr, a_nl);
+ if (a_nl > 16)
+ put_long (namelen, 16);
+ return 0;
+ }
+ }
+
+ return -1;
+}
+
+uae_u32 host_getpeername(TrapContext *ctx, SB, uae_u32 sd, uae_u32 name, uae_u32 namelen)
+{
+ int s;
+ socklen_t len = sizeof (struct sockaddr_in);
+ struct sockaddr_in addr{};
+
+ write_log("getpeername(%u, 0x%x, %u) -> ", sd, name, len);
+
+ s = getsock(ctx, sb, sd + 1);
+
+ if (s != INVALID_SOCKET) {
+ if (getpeername (s, (struct sockaddr *)&addr, &len)) {
+ SETERRNO;
+ write_log("failed (%d)\n", sb->sb_errno);
+ } else {
+ int a_nl;
+ write_log("okay\n");
+ a_nl = get_long (namelen);
+ copysockaddr_n2a (name, &addr, a_nl);
+ if (a_nl > 16)
+ put_long (namelen, 16);
+ return 0;
+ }
+ }
+
+ return -1;
+}
+
+uae_u32 host_IoctlSocket(TrapContext *ctx, SB, uae_u32 sd, uae_u32 request, uae_u32 arg)
+{
+ sd++;
+ int sock = getsock(ctx, sb, sd);
+ int r, argval = get_long (arg);
+ long flags;
+
+ if (sock == INVALID_SOCKET) {
+ sb->resultval = -1;
+ bsdsocklib_seterrno (ctx, sb, 9); /* EBADF */
+ return -1;
+ }
+
+ if ((flags = fcntl (sock, F_GETFL)) == -1) {
+ SETERRNO;
+ return -1;
+ }
+
+ switch (request) {
+ case 0x4004667B: /* FIOGETOWN */
+ sb->ownertask = get_long (arg);
+ return 0;
+
+ case 0x8004667C: /* FIOSETOWN */
+ trap_put_long(ctx, arg,sb->ownertask);
+ return 0;
+ case 0x8004667D: /* FIOASYNC */
+#if defined(O_ASYNC)
+ r = fcntl (sock, F_SETFL, argval ? flags | O_ASYNC : flags & ~O_ASYNC);
+ return r;
+# else
+ /* O_ASYNC is only available on Linux and BSD systems */
+ return fcntl (sock, F_GETFL);
+# endif
+
+ case 0x8004667E: /* FIONBIO */
+ {
+ r = fcntl (sock, F_SETFL, argval ?
+ flags | O_NONBLOCK : flags & ~O_NONBLOCK);
+ if (argval) {
+ sb->ftable[sd-1] &= ~SF_BLOCKING;
+ } else {
+ sb->ftable[sd-1] |= SF_BLOCKING;
+ }
+ return r;
+ }
+
+ case 0x4004667F: /* FIONREAD */
+ {
+ int nbytes = 0;
+ r = ioctl(sock, FIONREAD, &nbytes);
+
+ if (r >= 0) {
+ put_long (arg, nbytes);
+ return 0;
+ }
+ break;
+ }
+
+ case 0x80106921: /* SIOCGIFADDR */
+ case 0x80106923: /* SIOCGIFDSTADDR */
+ case 0x80106925: /* SIOCGIFBRDADDR */
+ case 0x80106927: /* SIOCGIFNETMASK */
+ case 0xc0206911: /* SIOCGIFFLAGS */
+ {
+ struct ifreq ifr;
+ memset(&ifr, 0, sizeof(ifr));
+ // Read interface name from Amiga memory
+ for (int i = 0; i < IFNAMSIZ - 1; i++) {
+ ifr.ifr_name[i] = trap_get_byte(ctx, arg + i);
+ if (ifr.ifr_name[i] == 0) break;
+ }
+ ifr.ifr_name[IFNAMSIZ - 1] = 0;
+ r = ioctl(sock, request, &ifr);
+ if (r >= 0) {
+ // Write result back
+ if (request == 0xc0206911) { // SIOCGIFFLAGS
+ trap_put_word(ctx, arg + IFNAMSIZ, ifr.ifr_flags);
+ } else { // Address IOCTLs
+ copysockaddr_n2a(arg + IFNAMSIZ, (struct sockaddr_in*)&ifr.ifr_addr, 16);
+ }
+ }
+ return r;
+ }
+
+ case 0xc0086924: /* SIOCGIFCONF */
+ {
+ struct ifconf ifc;
+ char buf[1024];
+ ifc.ifc_len = sizeof(buf);
+ ifc.ifc_buf = buf;
+ r = ioctl(sock, SIOCGIFCONF, &ifc);
+ if (r >= 0) {
+ // Write back the interface list
+ trap_put_long(ctx, arg, ifc.ifc_len);
+ uae_u32 bufptr = trap_get_long(ctx, arg + 4);
+ if (bufptr) {
+ for (int i = 0; i < ifc.ifc_len && i < (int)sizeof(buf);) {
+ struct ifreq *ifr = (struct ifreq*)(buf + i);
+ // Write interface name
+ for (int j = 0; j < IFNAMSIZ; j++) {
+ trap_put_byte(ctx, bufptr + i + j, ifr->ifr_name[j]);
+ }
+ // Write address
+ copysockaddr_n2a(bufptr + i + IFNAMSIZ, (struct sockaddr_in*)&ifr->ifr_addr, 16);
+ i += sizeof(struct ifreq);
+ }
+ }
+ }
+ return r;
+ }
+
+ } /* end switch */
+
+ bsdsocklib_seterrno (ctx, sb, EINVAL);
+ return -1;
+}
+
+int host_CloseSocket(TrapContext *ctx, SB, int sd)
+{
+ int s = getsock(ctx, sb, sd + 1);
+ int retval;
+
+ if (s == INVALID_SOCKET) {
+ bsdsocklib_seterrno (ctx, sb, 9); /* EBADF */
+ return -1;
+ }
+
+ /*
+ if (checksd (sb, sd + 1) == 1) {
+ return 0;
+ }
+ */
+ write_log("CloseSocket Amiga: %d, NativeSide %d\n", sd, s);
+
+ // Unregister from event monitoring if registered
+ unregister_socket_events(sb, sd);
+ // Clear pending event flags to prevent stale GetSocketEvents on fd reuse
+ sb->ftable[sd] &= ~SET_ALL;
+
+ retval = close_socket (s);
+ SETERRNO;
+ releasesock (ctx, sb, sd + 1);
+ return retval;
+}
+
+static void fd_zero(TrapContext *ctx, uae_u32 fdset, uae_u32 nfds)
+{
+ unsigned int i;
+ for (i = 0; i < nfds; i += 32, fdset += 4)
+ trap_put_long(ctx, fdset,0);
+}
+
+uae_u32 bsdthr_WaitSelect(SB)
+{
+ fd_set sets[3];
+ int i, s, set, a_s, max;
+ uae_u32 a_set;
+ struct timeval tv {};
+ int r;
+ TrapContext* ctx = sb->context;
+
+ int nfds = sb->nfds;
+ if (nfds > sb->dtablesize) {
+ write_log(_T("BSDSOCK: WaitSelect nfds (%d) exceeds dtablesize (%d), clamping\n"), nfds, sb->dtablesize);
+ nfds = sb->dtablesize;
+ }
+
+ BSDTRACE((_T("WaitSelect: %d 0x%x 0x%x 0x%x 0x%x 0x%x\n"), sb->nfds, sb->sets[0], sb->sets[1], sb->sets[2], sb->timeout, sb->sigmp));
+
+ if (sb->timeout)
+ BSDTRACE((_T("WaitSelect: timeout %d %d\n"), get_long(sb->timeout), get_long(sb->timeout + 4)));
+
+ FD_ZERO(&sets[0]);
+ FD_ZERO(&sets[1]);
+ FD_ZERO(&sets[2]);
+
+ /* Set up the abort socket */
+ FD_SET(sb->sockabort[0], &sets[0]);
+ FD_SET(sb->sockabort[0], &sets[2]);
+ max = sb->sockabort[0];
+
+ for (set = 0; set < 3; set++) {
+ if (sb->sets[set] != 0) {
+ a_set = sb->sets[set];
+ for (i = 0; i < nfds; i++) {
+ if (bsd_amigaside_FD_ISSET(i, a_set)) {
+ s = getsock(ctx, sb, i + 1);
+ BSDTRACE((_T("WaitSelect: AmigaSide %d set. NativeSide %d.\n"), i, s));
+ if (s == -1) {
+ write_log(_T("BSDSOCK: WaitSelect() called with invalid descriptor %d in set %d.\n"), i, set);
+ } else {
+ FD_SET(s, &sets[set]);
+ if (max < s) max = s;
+ }
+ }
+ }
+ }
+ }
+
+ max++;
+
+ if (sb->timeout) {
+ tv.tv_sec = get_long(sb->timeout);
+ tv.tv_usec = get_long(sb->timeout + 4);
+ }
+
+ BSDTRACE((_T("Select going to select\n")));
+ r = select(max, &sets[0], &sets[1], &sets[2], (sb->timeout == 0) ? NULL : &tv);
+ BSDTRACE((_T("Select returns %d, errno is %d\n"), r, errno));
+ if (r > 0) {
+ /* Socket told us to abort */
+ if (FD_ISSET(sb->sockabort[0], &sets[0])) {
+ /* read from the pipe to reset it */
+ BSDTRACE((_T("WaitSelect aborted from signal\n")));
+ r = 0;
+ for (set = 0; set < 3; set++)
+ if (sb->sets[set] != 0)
+ bsd_amigaside_FD_ZERO(sb->sets[set], nfds);
+ clearsockabort(sb);
+ }
+ else
+ /* This is perhaps slightly inefficient, but I don't care.. */
+ for (set = 0; set < 3; set++) {
+ a_set = sb->sets[set];
+ if (a_set != 0) {
+ bsd_amigaside_FD_ZERO(a_set, nfds);
+ for (i = 0; i < nfds; i++) {
+ a_s = getsock(ctx, sb, i + 1);
+ if (!(a_s < 0)) {
+ if (FD_ISSET(a_s, &sets[set])) {
+ BSDTRACE((_T("WaitSelect: NativeSide %d set. AmigaSide %d.\n"), a_s, i));
+
+ bsd_amigaside_FD_SET(i, a_set);
+ }
+ }
+ }
+ }
+ }
+ } else if (r == 0) { /* Timeout. I think we're supposed to clear the sets.. */
+ for (set = 0; set < 3; set++)
+ if (sb->sets[set] != 0)
+ bsd_amigaside_FD_ZERO(sb->sets[set], nfds);
+ }
+ BSDTRACE((_T("WaitSelect: r=%d errno=%d\n"), r, errno));
+ return r;
+}
+
+void host_WaitSelect(TrapContext *ctx, SB, uae_u32 nfds, uae_u32 readfds, uae_u32 writefds, uae_u32 exceptfds, uae_u32 timeout, uae_u32 sigmp)
+{
+ uae_u32 wssigs = (sigmp) ? trap_get_long(ctx, sigmp) : 0;
+ uae_u32 sigs;
+
+ if (wssigs) {
+ trap_call_add_dreg(ctx, 0, 0);
+ trap_call_add_dreg(ctx, 1, wssigs);
+ sigs = trap_call_lib(ctx, sb->sysbase, -0x132) & wssigs; // SetSignal()
+ if (sigs) {
+ put_long (sigmp, sigs);
+ // Check for zero address -> otherwise WinUAE crashes
+ if (readfds)
+ fd_zero(ctx, readfds,nfds);
+ if (writefds)
+ fd_zero(ctx, writefds,nfds);
+ if (exceptfds)
+ fd_zero(ctx, exceptfds,nfds);
+ sb->resultval = 0;
+ bsdsocklib_seterrno (ctx, sb, 0);
+ return;
+ }
+ }
+
+ if (nfds == 0 && wssigs == 0 && timeout == 0) {
+ /* Nothing to wait for: no sockets, no signals, no timeout */
+ if (readfds)
+ fd_zero(ctx, readfds, nfds);
+ if (writefds)
+ fd_zero(ctx, writefds, nfds);
+ if (exceptfds)
+ fd_zero(ctx, exceptfds, nfds);
+ sb->resultval = 0;
+ bsdsocklib_seterrno(ctx, sb, 0);
+ return;
+ }
+
+ sb->nfds = nfds;
+ sb->sets [0] = readfds;
+ sb->sets [1] = writefds;
+ sb->sets [2] = exceptfds;
+ sb->timeout = timeout;
+ sb->sigmp = wssigs;
+ sb->action = 5;
+
+ uae_sem_post (&sb->sem);
+
+ trap_call_add_dreg(ctx, 0, (((uae_u32)1) << sb->signal) | sb->eintrsigs | wssigs);
+ sigs = trap_call_lib(ctx, sb->sysbase, -0x13e); // Wait()
+
+ if (sigmp)
+ trap_put_long(ctx, sigmp, sigs & wssigs);
+
+ if (sigs & wssigs) {
+ /* Received the signals we were waiting on */
+ BSDTRACE((_T("WaitSelect: got signal(s) %x\n"), sigs));
+
+
+ if (!(sigs & (((uae_u32)1) << sb->signal))) {
+ sockabort (sb);
+ WAITSIGNAL;
+ sb->resultval = 0;
+ }
+ /*
+ if (readfds)
+ fd_zero(ctx, readfds, nfds);
+ if (writefds)
+ fd_zero(ctx, writefds, nfds);
+ if (exceptfds)
+ fd_zero(ctx, exceptfds, nfds);
+ */
+
+ bsdsocklib_seterrno (ctx, sb, 0);
+ } else if (sigs & sb->eintrsigs) {
+ /* Wait select was interrupted */
+ BSDTRACE((_T("WaitSelect: interrupted\n")));
+
+ if (!(sigs & (((uae_u32)1) << sb->signal))) {
+ sockabort (sb);
+ WAITSIGNAL;
+ }
+
+ sb->resultval = -1;
+ bsdsocklib_seterrno (ctx, sb, mapErrno (EINTR));
+ }
+ clearsockabort(sb);
+}
+
+uae_u32 host_Inet_NtoA(TrapContext *ctx, SB, uae_u32 in)
+{
+ uae_char *addr;
+ struct in_addr ina;
+ uae_u32 scratchbuf;
+
+ *(uae_u32 *)&ina = htonl(in);
+ BSDTRACE((_T("Inet_NtoA(%x) -> "), in));
+
+ if ((addr = inet_ntoa(ina)) != NULL) {
+ scratchbuf = trap_get_areg(ctx, 6) + offsetof(struct UAEBSDBase, scratchbuf);
+ strncpyha(ctx, scratchbuf, addr, SCRATCHBUFSIZE);
+ if (ISBSDTRACE) {
+ TCHAR *s = au(addr);
+ BSDTRACE((_T("%s\n"), s));
+ xfree(s);
+ }
+ return scratchbuf;
+ }
+ SETERRNO;
+ BSDTRACE((_T("failed (%d)\n"), sb->sb_errno));
+ return 0;
+}
+
+uae_u32 host_inet_addr(TrapContext *ctx, uae_u32 cp)
+{
+ uae_u32 addr;
+ char *cp_rp;
+
+ if (!trap_valid_address(ctx, cp, 4)) {
+ return 0;
+ }
+ cp_rp = trap_get_alloc_string(ctx, cp, 256);
+ addr = htonl(inet_addr(cp_rp));
+ if (ISBSDTRACE) {
+ TCHAR *s = au(cp_rp);
+ BSDTRACE((_T("inet_addr(%s) -> 0x%08x\n"), s, addr));
+ xfree(s);
+ }
+ xfree(cp_rp);
+ return addr;
+}
+
+uae_u32 host_gethostname(TrapContext *ctx, uae_u32 name, uae_u32 namelen)
+{
+ uae_char buf[256];
+ size_t len;
+
+ if (!trap_valid_address(ctx, name, namelen)) {
+ return -1;
+ }
+ if (gethostname(buf, sizeof buf) < 0) {
+ return -1;
+ }
+ buf[sizeof buf - 1] = 0;
+ len = strlen(buf) + 1;
+ if (len > namelen) {
+ len = namelen;
+ }
+ trap_put_bytes(ctx, buf, name, (int)len);
+ return 0;
+}
+
+// --- Wrap getservbyname, getservbyport, getprotobyname, getprotobynumber with mutex ---
+
+void host_getprotobyname (TrapContext *ctx, SB, uae_u32 name)
+{
+#if defined(__linux__)
+ struct protoent *p = getprotobyname ((char *)get_real_address (name));
+#else
+ // Thread safety: protect non-reentrant getprotobyname
+ std::lock_guard<std::mutex> lock(bsdsock_mutex);
+ struct protoent *p = getprotobyname ((char *)get_real_address (name));
+#endif
+ write_log("Getprotobyname(%s) = %p\n", get_real_address (name), p);
+ if (p == NULL) {
+ SETHERRNO;
+ SETERRNO;
+ return;
+ }
+ copyProtoent(ctx, sb, p);
+}
+
+void host_getprotobynumber(TrapContext *ctx, SB, uae_u32 number)
+{
+#if defined(__linux__)
+ struct protoent *p = getprotobynumber(number);
+#else
+ // Thread safety: protect non-reentrant getprotobynumber
+ std::lock_guard<std::mutex> lock(bsdsock_mutex);
+ struct protoent *p = getprotobynumber(number);
+#endif
+ write_log("getprotobynumber(%d) = %p\n", number, p);
+ if (p == NULL) {
+ SETHERRNO;
+ SETERRNO;
+ return;
+ }
+ copyProtoent(ctx, sb, p);
+}
+
+void host_getservbynameport(TrapContext *ctx, SB, uae_u32 nameport, uae_u32 proto, uae_u32 type)
+{
+ struct servent *s;
+#if defined(__linux__)
+ s = (type) ?
+ getservbyport (htons((unsigned short)nameport), (char *)get_real_address (proto)) :
+ getservbyname ((char *)get_real_address (nameport), (char *)get_real_address (proto));
+#else
+ // Thread safety: protect non-reentrant getservby* functions
+ std::lock_guard<std::mutex> lock(bsdsock_mutex);
+ s = (type) ?
+ getservbyport (htons((unsigned short)nameport), (char *)get_real_address (proto)) :
+ getservbyname ((char *)get_real_address (nameport), (char *)get_real_address (proto));
+#endif
+ int size;
+ int numaliases = 0;
+ uae_u32 aptr;
+ int i;
+ if (type) {
+ write_log("Getservbyport(%d, %s) = %p\n", nameport, get_real_address (proto), s);
+ } else {
+ write_log("Getservbyname(%s, %s) = %p\n", get_real_address (nameport), get_real_address (proto), s);
+ }
+ if (s != NULL) {
+ // compute total size of servent
+ size = 20;
+ if (s->s_name != NULL)
+ size += strlen (s->s_name) + 1;
+ if (s->s_proto != NULL)
+ size += strlen (s->s_proto) + 1;
+
+ if (s->s_aliases != NULL)
+ while (s->s_aliases[numaliases])
+ size += strlen (s->s_aliases[numaliases++]) + 5;
+
+ if (sb->servent) {
+ uae_FreeMem(ctx, sb->servent, sb->serventsize, sb->sysbase);
+ }
+
+ sb->servent = uae_AllocMem (ctx, size, 0, sb->sysbase);
+
+ if (!sb->servent) {
+ write_log ("BSDSOCK: WARNING - getservby%s() ran out of Amiga memory (couldn't allocate %d bytes)\n",type ? "port" : "name", size);
+ bsdsocklib_seterrno (ctx, sb, 12); // ENOMEM
+ return;
+ }
+
+ sb->serventsize = size;
+
+ aptr = sb->servent + 20 + numaliases * 4;
+
+ // transfer servent to Amiga memory
+ trap_put_long(ctx, sb->servent + 4, sb->servent + 16);
+ trap_put_long(ctx, sb->servent + 8, (unsigned short)htons (s->s_port));
+
+ for (i = 0; i < numaliases; i++)
+ trap_put_long(ctx, sb->servent + 16 + i * 4, addstr_ansi(ctx, &aptr, s->s_aliases[i]));
+ trap_put_long(ctx, sb->servent + 16 + numaliases * 4, 0);
+ trap_put_long(ctx, sb->servent, aptr);
+ addstr_ansi(ctx, &aptr, s->s_name);
+ trap_put_long(ctx, sb->servent + 12, aptr);
+ addstr_ansi(ctx, &aptr, s->s_proto);
+
+ bsdsocklib_seterrno (ctx, sb,0);
+ } else {
+ // Free previous allocation and clear so Amiga side returns NULL
+ if (sb->servent) {
+ uae_FreeMem(ctx, sb->servent, sb->serventsize, sb->sysbase);
+ }
+ sb->servent = 0;
+ SETHERRNO;
+ SETERRNO;
+ return;
+ }
+}
+
+void host_gethostbynameaddr (TrapContext *ctx, SB, uae_u32 name, uae_u32 namelen, long addrtype)
+{
+ sb->name = name;
+ sb->a_addrlen = namelen;
+ sb->flags = addrtype;
+ if (addrtype == -1)
+ sb->action = 4;
+ else
+ sb->action = 7;
+
+ uae_sem_post (&sb->sem);
+
+ WAITSIGNAL;
+}
--- /dev/null
+#include "sysconfig.h"
+#include "sysdeps.h"
+
+#include <limits.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <string>
+#include <vector>
+
+#ifdef WINUAE_UNIX_WITH_IMAGEIO
+#include <CoreFoundation/CoreFoundation.h>
+#include <CoreGraphics/CoreGraphics.h>
+#include <ImageIO/ImageIO.h>
+#define UAE_UNIX_CLIPBOARD_IMAGEIO 1
+#endif
+
+#ifdef UAE_UNIX_WITH_SDL3
+#include <SDL3/SDL.h>
+#if SDL_VERSION_ATLEAST(3, 2, 0)
+#define UAE_UNIX_WITH_SDL3_CLIPBOARD_DATA 1
+#endif
+#endif
+
+#ifdef WINUAE_UNIX_WITH_LIBPNG
+#include <png.h>
+#if defined(PNG_SIMPLIFIED_READ_SUPPORTED) && defined(PNG_SIMPLIFIED_WRITE_SUPPORTED)
+#define UAE_UNIX_CLIPBOARD_PNG 1
+#endif
+#endif
+
+#include "traps.h"
+#include "clipboard.h"
+#include "keybuf.h"
+#include "native2amiga_api.h"
+#include "uae.h"
+
+extern bool filesys_heartbeat(void);
+
+static constexpr size_t CLIP_SIZE_LIMIT = 10000000;
+static constexpr size_t CLIP_SIZE_LIMIT_INIT = 30000;
+
+static uaecptr clipboard_data;
+static int vdelay, vdelay2;
+static int signaling, initialized;
+static std::vector<uae_u8> to_amiga;
+static int to_amiga_phase;
+static bool clip_disabled;
+static int host_poll_delay;
+static std::string last_host_clipboard;
+static bool last_host_clipboard_valid;
+static std::vector<uae_u8> last_host_clipboard_bitmap;
+static bool last_host_clipboard_bitmap_valid;
+
+static bool read_command_output(const char *command, std::string *out, size_t max_bytes)
+{
+ FILE *pipe = popen(command, "r");
+ if (!pipe) {
+ return false;
+ }
+
+ char buffer[4096];
+ const size_t read_limit = max_bytes + 1;
+ while (out->size() < read_limit) {
+ const size_t remaining = read_limit - out->size();
+ const size_t wanted = remaining < sizeof buffer ? remaining : sizeof buffer;
+ const size_t got = fread(buffer, 1, wanted, pipe);
+ if (got > 0) {
+ out->append(buffer, got);
+ }
+ if (got < wanted) {
+ if (feof(pipe) || ferror(pipe)) {
+ break;
+ }
+ }
+ }
+
+ const bool too_large = out->size() > max_bytes;
+ const int status = pclose(pipe);
+ if (too_large) {
+ out->clear();
+ return false;
+ }
+ return status == 0;
+}
+
+static bool write_command_input(const char *command, const std::string &text)
+{
+ FILE *pipe = popen(command, "w");
+ if (!pipe) {
+ return false;
+ }
+ if (!text.empty() && fwrite(text.data(), 1, text.size(), pipe) != text.size()) {
+ pclose(pipe);
+ return false;
+ }
+ return pclose(pipe) == 0;
+}
+
+#ifdef UAE_UNIX_WITH_SDL3_CLIPBOARD_DATA
+struct UnixClipboardData {
+ std::vector<uae_u8> bmp;
+ std::vector<uae_u8> png;
+ std::vector<uae_u8> tiff;
+};
+
+static const void *SDLCALL unix_clipboard_data_callback(void *userdata, const char *mime_type, size_t *size)
+{
+ UnixClipboardData *data = static_cast<UnixClipboardData *>(userdata);
+ if (!data) {
+ if (size) {
+ *size = 0;
+ }
+ return NULL;
+ }
+ const std::vector<uae_u8> *payload = NULL;
+#ifdef UAE_UNIX_CLIPBOARD_PNG
+ if (mime_type && !strcmp(mime_type, "image/png") && !data->png.empty()) {
+ payload = &data->png;
+ }
+#endif
+#ifdef UAE_UNIX_CLIPBOARD_IMAGEIO
+ if (!payload && mime_type &&
+ (!strcmp(mime_type, "image/tiff") || !strcmp(mime_type, "image/tif") ||
+ !strcmp(mime_type, "public.tiff")) && !data->tiff.empty()) {
+ payload = &data->tiff;
+ }
+#endif
+ if (!payload && mime_type &&
+ (!strcmp(mime_type, "image/bmp") || !strcmp(mime_type, "image/x-bmp")) && !data->bmp.empty()) {
+ payload = &data->bmp;
+ }
+ if (!payload) {
+ payload = !data->bmp.empty() ? &data->bmp : &data->png;
+ }
+ if (size) {
+ *size = payload ? payload->size() : 0;
+ }
+ return payload ? payload->data() : NULL;
+}
+
+static void SDLCALL unix_clipboard_data_cleanup(void *userdata)
+{
+ delete static_cast<UnixClipboardData *>(userdata);
+}
+
+static bool has_host_clipboard_image(void)
+{
+ return
+#ifdef UAE_UNIX_CLIPBOARD_PNG
+ SDL_HasClipboardData("image/png") ||
+#endif
+#ifdef UAE_UNIX_CLIPBOARD_IMAGEIO
+ SDL_HasClipboardData("image/tiff") || SDL_HasClipboardData("image/tif") ||
+ SDL_HasClipboardData("public.tiff") ||
+#endif
+ SDL_HasClipboardData("image/bmp") || SDL_HasClipboardData("image/x-bmp");
+}
+
+static bool read_host_clipboard_data(const char * const *mime_types, std::vector<uae_u8> *out, size_t max_bytes)
+{
+ for (int i = 0; mime_types[i]; i++) {
+ if (!SDL_HasClipboardData(mime_types[i])) {
+ continue;
+ }
+ size_t size = 0;
+ void *data = SDL_GetClipboardData(mime_types[i], &size);
+ if (!data) {
+ continue;
+ }
+ if (size > max_bytes) {
+ SDL_free(data);
+ return false;
+ }
+ out->assign((const uae_u8 *)data, (const uae_u8 *)data + size);
+ SDL_free(data);
+ return true;
+ }
+ return false;
+}
+
+#ifdef UAE_UNIX_CLIPBOARD_PNG
+static bool read_host_clipboard_png(std::vector<uae_u8> *png, size_t max_bytes)
+{
+ const char *mime_types[] = { "image/png", NULL };
+ return read_host_clipboard_data(mime_types, png, max_bytes);
+}
+#endif
+
+#ifdef UAE_UNIX_CLIPBOARD_IMAGEIO
+static bool read_host_clipboard_tiff(std::vector<uae_u8> *tiff, size_t max_bytes)
+{
+ const char *mime_types[] = { "image/tiff", "image/tif", "public.tiff", NULL };
+ return read_host_clipboard_data(mime_types, tiff, max_bytes);
+}
+#endif
+
+static bool read_host_clipboard_bmp(std::vector<uae_u8> *bmp, size_t max_bytes)
+{
+ const char *mime_types[] = { "image/bmp", "image/x-bmp", NULL };
+ return read_host_clipboard_data(mime_types, bmp, max_bytes);
+}
+
+static bool write_host_clipboard_image(const std::vector<uae_u8> &bmp, const std::vector<uae_u8> *png,
+ const std::vector<uae_u8> *tiff)
+{
+ UnixClipboardData *data = new UnixClipboardData;
+ data->bmp = bmp;
+ if (png) {
+ data->png = *png;
+ }
+ if (tiff) {
+ data->tiff = *tiff;
+ }
+ const bool cache_png = png && !png->empty();
+ const bool cache_tiff = tiff && !tiff->empty();
+
+ const char *mime_types[] = {
+#ifdef UAE_UNIX_CLIPBOARD_PNG
+ "image/png",
+#endif
+#ifdef UAE_UNIX_CLIPBOARD_IMAGEIO
+ "image/tiff", "image/tif", "public.tiff",
+#endif
+ "image/bmp", "image/x-bmp"
+ };
+ if (!SDL_SetClipboardData(unix_clipboard_data_callback, unix_clipboard_data_cleanup,
+ data, mime_types, sizeof mime_types / sizeof mime_types[0])) {
+ write_log(_T("clipboard: failed to write host clipboard bitmap: %s\n"), SDL_GetError());
+ delete data;
+ return false;
+ }
+
+ last_host_clipboard.clear();
+ last_host_clipboard_valid = false;
+ last_host_clipboard_bitmap = cache_png ? *png : (cache_tiff ? *tiff : bmp);
+ last_host_clipboard_bitmap_valid = true;
+ return true;
+}
+
+static bool write_host_clipboard_bmp(const std::vector<uae_u8> &bmp)
+{
+ return write_host_clipboard_image(bmp, NULL, NULL);
+}
+#endif
+
+static bool read_host_clipboard_text(std::string *text, size_t max_bytes)
+{
+#ifdef UAE_UNIX_WITH_SDL3
+ if (SDL_HasClipboardText()) {
+ char *clipboard = SDL_GetClipboardText();
+ if (clipboard) {
+ text->assign(clipboard);
+ SDL_free(clipboard);
+ if (text->size() > max_bytes) {
+ text->clear();
+ return false;
+ }
+ return true;
+ }
+ }
+#ifdef UAE_UNIX_WITH_SDL3_CLIPBOARD_DATA
+ if (has_host_clipboard_image()) {
+ return false;
+ }
+#endif
+#endif
+
+ const char *commands[] = {
+#ifdef __APPLE__
+ "/usr/bin/pbpaste",
+#else
+ "wl-paste --no-newline 2>/dev/null",
+ "xclip -selection clipboard -o 2>/dev/null",
+ "xsel --clipboard --output 2>/dev/null",
+#endif
+ NULL
+ };
+
+ for (int i = 0; commands[i]; i++) {
+ text->clear();
+ if (read_command_output(commands[i], text, max_bytes)) {
+ return true;
+ }
+ }
+ text->clear();
+ return false;
+}
+
+static bool write_host_clipboard_text(const std::string &text)
+{
+#ifdef UAE_UNIX_WITH_SDL3
+ if (SDL_SetClipboardText(text.c_str())) {
+ last_host_clipboard = text;
+ last_host_clipboard_valid = true;
+ last_host_clipboard_bitmap.clear();
+ last_host_clipboard_bitmap_valid = false;
+ return true;
+ }
+#endif
+
+ const char *commands[] = {
+#ifdef __APPLE__
+ "/usr/bin/pbcopy",
+#else
+ "wl-copy 2>/dev/null",
+ "xclip -selection clipboard 2>/dev/null",
+ "xsel --clipboard --input 2>/dev/null",
+#endif
+ NULL
+ };
+
+ for (int i = 0; commands[i]; i++) {
+ if (write_command_input(commands[i], text)) {
+ last_host_clipboard = text;
+ last_host_clipboard_valid = true;
+ last_host_clipboard_bitmap.clear();
+ last_host_clipboard_bitmap_valid = false;
+ return true;
+ }
+ }
+ return false;
+}
+
+static void normalize_text_for_keybuf(std::string *text)
+{
+ std::string normalized;
+ normalized.reserve(text->size());
+ for (size_t i = 0; i < text->size(); i++) {
+ const char ch = (*text)[i];
+ if (ch == '\r') {
+ if (i + 1 < text->size() && (*text)[i + 1] == '\n') {
+ continue;
+ }
+ normalized.push_back('\n');
+ } else {
+ normalized.push_back(ch);
+ }
+ }
+ *text = normalized;
+}
+
+static std::string host_text_to_amiga_text(const std::string &text)
+{
+ std::string converted;
+ converted.reserve(text.size());
+ for (size_t i = 0; i < text.size(); i++) {
+ if (text[i] != '\r') {
+ converted.push_back(text[i]);
+ }
+ }
+ return converted;
+}
+
+static void append_be32(std::vector<uae_u8> *out, uae_u32 value)
+{
+ out->push_back(value >> 24);
+ out->push_back(value >> 16);
+ out->push_back(value >> 8);
+ out->push_back(value);
+}
+
+static void append_be16(std::vector<uae_u8> *out, uae_u16 value)
+{
+ out->push_back(value >> 8);
+ out->push_back(value);
+}
+
+static void append_le16(std::vector<uae_u8> *out, uae_u16 value)
+{
+ out->push_back(value);
+ out->push_back(value >> 8);
+}
+
+static void append_le32(std::vector<uae_u8> *out, uae_u32 value)
+{
+ out->push_back(value);
+ out->push_back(value >> 8);
+ out->push_back(value >> 16);
+ out->push_back(value >> 24);
+}
+
+static uae_u16 read_le16(const uae_u8 *p)
+{
+ return (uae_u16)p[0] | ((uae_u16)p[1] << 8);
+}
+
+static uae_u32 read_le32(const uae_u8 *p)
+{
+ return (uae_u32)p[0] | ((uae_u32)p[1] << 8) | ((uae_u32)p[2] << 16) | ((uae_u32)p[3] << 24);
+}
+
+static uae_u16 read_be16(const uae_u8 *p)
+{
+ return ((uae_u16)p[0] << 8) | p[1];
+}
+
+static uae_u32 read_be32(const uae_u8 *p)
+{
+ return ((uae_u32)p[0] << 24) | ((uae_u32)p[1] << 16) | ((uae_u32)p[2] << 8) | p[3];
+}
+
+static void append_literal(std::vector<uae_u8> *out, const char *literal)
+{
+ for (int i = 0; i < 4; i++) {
+ out->push_back((uae_u8)literal[i]);
+ }
+}
+
+struct UnixClipboardBitmap {
+ int width = 0;
+ int height = 0;
+ std::vector<uae_u32> pixels;
+};
+
+static bool parse_bmp(const std::vector<uae_u8> &bmp, UnixClipboardBitmap *bitmap)
+{
+ if (!bitmap || bmp.size() < 54 || bmp[0] != 'B' || bmp[1] != 'M') {
+ return false;
+ }
+
+ const uae_u32 pixel_offset = read_le32(&bmp[10]);
+ const uae_u32 header_size = read_le32(&bmp[14]);
+ if (header_size < 40 || 14 + header_size > bmp.size()) {
+ return false;
+ }
+
+ const int width = (int)read_le32(&bmp[18]);
+ const int signed_height = (int)read_le32(&bmp[22]);
+ const int height = signed_height < 0 ? -signed_height : signed_height;
+ const bool top_down = signed_height < 0;
+ const int planes = read_le16(&bmp[26]);
+ const int bpp = read_le16(&bmp[28]);
+ const uae_u32 compression = read_le32(&bmp[30]);
+ if (width <= 0 || height <= 0 || planes != 1 || compression != 0 ||
+ (bpp != 8 && bpp != 24 && bpp != 32)) {
+ return false;
+ }
+
+ const size_t rowbytes = ((size_t)width * (size_t)bpp + 31) / 32 * 4;
+ if (pixel_offset > bmp.size() || rowbytes > (bmp.size() - pixel_offset) / (size_t)height) {
+ return false;
+ }
+
+ std::vector<uae_u32> palette;
+ if (bpp == 8) {
+ uae_u32 colors = read_le32(&bmp[46]);
+ if (!colors) {
+ colors = 256;
+ }
+ const size_t palette_offset = 14 + header_size;
+ if (palette_offset + (size_t)colors * 4 > bmp.size()) {
+ return false;
+ }
+ palette.reserve(colors);
+ for (uae_u32 i = 0; i < colors; i++) {
+ const uae_u8 *entry = &bmp[palette_offset + (size_t)i * 4];
+ palette.push_back(((uae_u32)entry[2] << 16) | ((uae_u32)entry[1] << 8) | entry[0]);
+ }
+ }
+
+ bitmap->width = width;
+ bitmap->height = height;
+ bitmap->pixels.assign((size_t)width * (size_t)height, 0);
+ for (int y = 0; y < height; y++) {
+ const int src_y = top_down ? y : height - 1 - y;
+ const uae_u8 *src = &bmp[pixel_offset + (size_t)src_y * rowbytes];
+ for (int x = 0; x < width; x++) {
+ uae_u32 pixel = 0;
+ if (bpp == 8) {
+ const uae_u8 index = src[x];
+ if (index < palette.size()) {
+ pixel = palette[index];
+ }
+ } else if (bpp == 24) {
+ const uae_u8 *p = src + (size_t)x * 3;
+ pixel = ((uae_u32)p[2] << 16) | ((uae_u32)p[1] << 8) | p[0];
+ } else {
+ const uae_u8 *p = src + (size_t)x * 4;
+ pixel = ((uae_u32)p[2] << 16) | ((uae_u32)p[1] << 8) | p[0];
+ }
+ bitmap->pixels[(size_t)y * (size_t)width + x] = pixel;
+ }
+ }
+ return true;
+}
+
+static bool make_bmp(const UnixClipboardBitmap &bitmap, std::vector<uae_u8> *bmp)
+{
+ if (!bmp || bitmap.width <= 0 || bitmap.height <= 0 ||
+ bitmap.pixels.size() < (size_t)bitmap.width * (size_t)bitmap.height) {
+ return false;
+ }
+
+ const int width = bitmap.width;
+ const int height = bitmap.height;
+ const size_t rowbytes = ((size_t)width * 3 + 3) & ~3;
+ const uae_u32 image_size = (uae_u32)(rowbytes * (size_t)height);
+ const uae_u32 file_size = 14 + 40 + image_size;
+
+ bmp->clear();
+ bmp->reserve(file_size);
+ bmp->push_back('B');
+ bmp->push_back('M');
+ append_le32(bmp, file_size);
+ append_le16(bmp, 0);
+ append_le16(bmp, 0);
+ append_le32(bmp, 14 + 40);
+ append_le32(bmp, 40);
+ append_le32(bmp, (uae_u32)width);
+ append_le32(bmp, (uae_u32)height);
+ append_le16(bmp, 1);
+ append_le16(bmp, 24);
+ append_le32(bmp, 0);
+ append_le32(bmp, image_size);
+ append_le32(bmp, 0);
+ append_le32(bmp, 0);
+ append_le32(bmp, 0);
+ append_le32(bmp, 0);
+
+ std::vector<uae_u8> row(rowbytes);
+ for (int y = height - 1; y >= 0; y--) {
+ memset(row.data(), 0, row.size());
+ for (int x = 0; x < width; x++) {
+ const uae_u32 pixel = bitmap.pixels[(size_t)y * (size_t)width + x];
+ row[(size_t)x * 3 + 0] = pixel;
+ row[(size_t)x * 3 + 1] = pixel >> 8;
+ row[(size_t)x * 3 + 2] = pixel >> 16;
+ }
+ bmp->insert(bmp->end(), row.begin(), row.end());
+ }
+ return true;
+}
+
+#ifdef UAE_UNIX_CLIPBOARD_PNG
+static bool parse_png(const std::vector<uae_u8> &png, UnixClipboardBitmap *bitmap)
+{
+ if (!bitmap || png.empty()) {
+ return false;
+ }
+
+ png_image image;
+ memset(&image, 0, sizeof image);
+ image.version = PNG_IMAGE_VERSION;
+ if (!png_image_begin_read_from_memory(&image, png.data(), png.size())) {
+ return false;
+ }
+ if (image.width == 0 || image.height == 0 ||
+ (uae_u64)image.width * (uae_u64)image.height > CLIP_SIZE_LIMIT / 4) {
+ png_image_free(&image);
+ return false;
+ }
+
+ image.format = PNG_FORMAT_RGBA;
+ std::vector<uae_u8> rgba(PNG_IMAGE_SIZE(image));
+ if (!png_image_finish_read(&image, NULL, rgba.data(), 0, NULL)) {
+ png_image_free(&image);
+ return false;
+ }
+
+ bitmap->width = (int)image.width;
+ bitmap->height = (int)image.height;
+ bitmap->pixels.assign((size_t)image.width * (size_t)image.height, 0);
+ for (png_uint_32 y = 0; y < image.height; y++) {
+ const uae_u8 *row = rgba.data() + (size_t)y * image.width * 4;
+ for (png_uint_32 x = 0; x < image.width; x++) {
+ const uae_u8 *p = row + (size_t)x * 4;
+ const uae_u32 alpha = p[3];
+ const uae_u32 red = (p[0] * alpha + 255 * (255 - alpha) + 127) / 255;
+ const uae_u32 green = (p[1] * alpha + 255 * (255 - alpha) + 127) / 255;
+ const uae_u32 blue = (p[2] * alpha + 255 * (255 - alpha) + 127) / 255;
+ bitmap->pixels[(size_t)y * image.width + x] = (red << 16) | (green << 8) | blue;
+ }
+ }
+ png_image_free(&image);
+ return true;
+}
+
+static bool make_png(const UnixClipboardBitmap &bitmap, std::vector<uae_u8> *png)
+{
+ if (!png || bitmap.width <= 0 || bitmap.height <= 0 ||
+ bitmap.pixels.size() < (size_t)bitmap.width * (size_t)bitmap.height) {
+ return false;
+ }
+
+ std::vector<uae_u8> rgb((size_t)bitmap.width * (size_t)bitmap.height * 3);
+ for (int y = 0; y < bitmap.height; y++) {
+ uae_u8 *row = rgb.data() + (size_t)y * bitmap.width * 3;
+ for (int x = 0; x < bitmap.width; x++) {
+ const uae_u32 pixel = bitmap.pixels[(size_t)y * bitmap.width + x];
+ row[(size_t)x * 3 + 0] = pixel >> 16;
+ row[(size_t)x * 3 + 1] = pixel >> 8;
+ row[(size_t)x * 3 + 2] = pixel;
+ }
+ }
+
+ png_image image;
+ memset(&image, 0, sizeof image);
+ image.version = PNG_IMAGE_VERSION;
+ image.width = bitmap.width;
+ image.height = bitmap.height;
+ image.format = PNG_FORMAT_RGB;
+
+ png_alloc_size_t size = 0;
+ if (!png_image_write_to_memory(&image, NULL, &size, 0, rgb.data(), 0, NULL) || !size) {
+ png_image_free(&image);
+ return false;
+ }
+ png->resize(size);
+ if (!png_image_write_to_memory(&image, png->data(), &size, 0, rgb.data(), 0, NULL)) {
+ png_image_free(&image);
+ png->clear();
+ return false;
+ }
+ png->resize(size);
+ png_image_free(&image);
+ return true;
+}
+#endif
+
+#ifdef UAE_UNIX_CLIPBOARD_IMAGEIO
+static bool parse_imageio_bitmap(const std::vector<uae_u8> &data, UnixClipboardBitmap *bitmap)
+{
+ if (!bitmap || data.empty()) {
+ return false;
+ }
+
+ CFDataRef image_data = CFDataCreate(kCFAllocatorDefault, data.data(), data.size());
+ if (!image_data) {
+ return false;
+ }
+ CGImageSourceRef source = CGImageSourceCreateWithData(image_data, NULL);
+ CFRelease(image_data);
+ if (!source) {
+ return false;
+ }
+
+ CGImageRef image = CGImageSourceCreateImageAtIndex(source, 0, NULL);
+ CFRelease(source);
+ if (!image) {
+ return false;
+ }
+
+ const size_t width = CGImageGetWidth(image);
+ const size_t height = CGImageGetHeight(image);
+ if (!width || !height || width > INT_MAX || height > INT_MAX ||
+ (uae_u64)width * (uae_u64)height > CLIP_SIZE_LIMIT / 4) {
+ CGImageRelease(image);
+ return false;
+ }
+
+ std::vector<uae_u8> rgba(width * height * 4);
+ CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
+ CGContextRef context = CGBitmapContextCreate(rgba.data(), width, height, 8, width * 4,
+ colorspace, kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast);
+ CGColorSpaceRelease(colorspace);
+ if (!context) {
+ CGImageRelease(image);
+ return false;
+ }
+
+ CGContextTranslateCTM(context, 0, (CGFloat)height);
+ CGContextScaleCTM(context, 1, -1);
+ CGContextDrawImage(context, CGRectMake(0, 0, (CGFloat)width, (CGFloat)height), image);
+ CGContextRelease(context);
+ CGImageRelease(image);
+
+ bitmap->width = (int)width;
+ bitmap->height = (int)height;
+ bitmap->pixels.assign(width * height, 0);
+ for (size_t y = 0; y < height; y++) {
+ const uae_u8 *row = rgba.data() + y * width * 4;
+ for (size_t x = 0; x < width; x++) {
+ const uae_u8 *p = row + x * 4;
+ const uae_u32 alpha = p[3];
+ const uae_u32 red = p[0] + 255 - alpha;
+ const uae_u32 green = p[1] + 255 - alpha;
+ const uae_u32 blue = p[2] + 255 - alpha;
+ bitmap->pixels[y * width + x] =
+ ((red > 255 ? 255 : red) << 16) |
+ ((green > 255 ? 255 : green) << 8) |
+ (blue > 255 ? 255 : blue);
+ }
+ }
+ return true;
+}
+
+static bool make_tiff(const UnixClipboardBitmap &bitmap, std::vector<uae_u8> *tiff)
+{
+ if (!tiff || bitmap.width <= 0 || bitmap.height <= 0 ||
+ bitmap.pixels.size() < (size_t)bitmap.width * (size_t)bitmap.height) {
+ return false;
+ }
+
+ const size_t width = bitmap.width;
+ const size_t height = bitmap.height;
+ std::vector<uae_u8> rgba(width * height * 4);
+ for (size_t y = 0; y < height; y++) {
+ uae_u8 *row = rgba.data() + y * width * 4;
+ for (size_t x = 0; x < width; x++) {
+ const uae_u32 pixel = bitmap.pixels[y * width + x];
+ uae_u8 *p = row + x * 4;
+ p[0] = pixel >> 16;
+ p[1] = pixel >> 8;
+ p[2] = pixel;
+ p[3] = 255;
+ }
+ }
+
+ CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, rgba.data(), rgba.size(), NULL);
+ if (!provider) {
+ return false;
+ }
+ CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
+ CGImageRef image = CGImageCreate(width, height, 8, 32, width * 4, colorspace,
+ kCGBitmapByteOrder32Big | kCGImageAlphaNoneSkipLast, provider, NULL, false,
+ kCGRenderingIntentDefault);
+ CGColorSpaceRelease(colorspace);
+ CGDataProviderRelease(provider);
+ if (!image) {
+ return false;
+ }
+
+ CFMutableDataRef image_data = CFDataCreateMutable(kCFAllocatorDefault, 0);
+ if (!image_data) {
+ CGImageRelease(image);
+ return false;
+ }
+ CGImageDestinationRef destination = CGImageDestinationCreateWithData(image_data, CFSTR("public.tiff"), 1, NULL);
+ if (!destination) {
+ CFRelease(image_data);
+ CGImageRelease(image);
+ return false;
+ }
+ CGImageDestinationAddImage(destination, image, NULL);
+ const bool ok = CGImageDestinationFinalize(destination);
+ CFRelease(destination);
+ CGImageRelease(image);
+ if (!ok) {
+ CFRelease(image_data);
+ return false;
+ }
+
+ const UInt8 *bytes = CFDataGetBytePtr(image_data);
+ const CFIndex length = CFDataGetLength(image_data);
+ tiff->assign(bytes, bytes + length);
+ CFRelease(image_data);
+ return !tiff->empty();
+}
+#endif
+
+static std::vector<uae_u8> make_iff_text(const std::string &host_text)
+{
+ const std::string amiga_text = host_text_to_amiga_text(host_text);
+ std::vector<uae_u8> iff;
+ const uae_u32 text_size = (uae_u32)amiga_text.size();
+ const uae_u32 form_size = 4 + 8 + text_size + (text_size & 1);
+
+ iff.reserve(form_size + 8);
+ append_literal(&iff, "FORM");
+ append_be32(&iff, form_size);
+ append_literal(&iff, "FTXT");
+ append_literal(&iff, "CHRS");
+ append_be32(&iff, text_size);
+ iff.insert(iff.end(), amiga_text.begin(), amiga_text.end());
+ if (text_size & 1) {
+ iff.push_back(0);
+ }
+ return iff;
+}
+
+static bool make_iff_ilbm(const UnixClipboardBitmap &bitmap, bool initial, std::vector<uae_u8> *iff)
+{
+ if (!iff || bitmap.width <= 0 || bitmap.height <= 0 ||
+ bitmap.pixels.size() < (size_t)bitmap.width * (size_t)bitmap.height) {
+ return false;
+ }
+
+ const size_t pixel_bytes = (size_t)bitmap.width * (size_t)bitmap.height * 4;
+ if (pixel_bytes > (initial ? CLIP_SIZE_LIMIT_INIT : CLIP_SIZE_LIMIT)) {
+ return false;
+ }
+
+ std::vector<uae_u32> colors;
+ colors.reserve(256);
+ for (uae_u32 pixel : bitmap.pixels) {
+ size_t i = 0;
+ for (; i < colors.size(); i++) {
+ if (colors[i] == pixel) {
+ break;
+ }
+ }
+ if (i == colors.size()) {
+ if (colors.size() >= 256) {
+ colors.clear();
+ break;
+ }
+ colors.push_back(pixel);
+ }
+ }
+
+ int planes = 24;
+ if (colors.size() >= 256) {
+ colors.clear();
+ }
+ if (!colors.empty()) {
+ planes = 0;
+ for (size_t count = colors.size(); count > 0; count >>= 1) {
+ planes++;
+ }
+ if (planes < 1) {
+ planes = 1;
+ }
+ }
+
+ const int width = bitmap.width;
+ const int height = bitmap.height;
+ const int plane_rowbytes = ((width + 15) & ~15) / 8;
+ const uae_u32 body_size = (uae_u32)(plane_rowbytes * height * planes);
+
+ iff->clear();
+ append_literal(iff, "FORM");
+ append_be32(iff, 0);
+ append_literal(iff, "ILBM");
+ append_literal(iff, "BMHD");
+ append_be32(iff, 20);
+ append_be16(iff, (uae_u16)width);
+ append_be16(iff, (uae_u16)height);
+ append_be16(iff, 0);
+ append_be16(iff, 0);
+ iff->push_back((uae_u8)planes);
+ iff->push_back(0);
+ iff->push_back(0);
+ iff->push_back(0);
+ append_be16(iff, 0);
+ iff->push_back(1);
+ iff->push_back(1);
+ append_be16(iff, (uae_u16)width);
+ append_be16(iff, (uae_u16)height);
+ append_literal(iff, "CAMG");
+ append_be32(iff, 4);
+ uae_u32 camg = 0;
+ if (width > 400) {
+ camg |= 0x8000;
+ }
+ if (height > 300) {
+ camg |= 0x0004;
+ }
+ append_be32(iff, camg);
+
+ if (planes <= 8) {
+ const int color_count = 1 << planes;
+ append_literal(iff, "CMAP");
+ append_be32(iff, color_count * 3);
+ for (int i = 0; i < color_count; i++) {
+ const uae_u32 color = i < (int)colors.size() ? colors[i] : 0;
+ iff->push_back(color >> 16);
+ iff->push_back(color >> 8);
+ iff->push_back(color);
+ }
+ if ((color_count * 3) & 1) {
+ iff->push_back(0);
+ }
+ }
+
+ append_literal(iff, "BODY");
+ append_be32(iff, body_size);
+ const size_t body_offset = iff->size();
+ iff->resize(body_offset + body_size, 0);
+ uae_u8 *body = iff->data() + body_offset;
+
+ for (int y = 0; y < height; y++) {
+ if (planes <= 8) {
+ for (int b = 0; b < planes; b++) {
+ for (int x = 0; x < width; x++) {
+ const uae_u32 pixel = bitmap.pixels[(size_t)y * (size_t)width + x];
+ int color_index = 0;
+ for (int i = 0; i < (int)colors.size(); i++) {
+ if (colors[i] == pixel) {
+ color_index = i;
+ break;
+ }
+ }
+ if (color_index & (1 << b)) {
+ body[x / 8] |= 1 << (7 - (x & 7));
+ }
+ }
+ body += plane_rowbytes;
+ }
+ } else {
+ for (int component = 0; component < 3; component++) {
+ for (int b = 0; b < 8; b++) {
+ const int mask = 1 << (((2 - component) * 8) + b);
+ for (int x = 0; x < width; x++) {
+ const uae_u32 pixel = bitmap.pixels[(size_t)y * (size_t)width + x];
+ if (pixel & mask) {
+ body[x / 8] |= 1 << (7 - (x & 7));
+ }
+ }
+ body += plane_rowbytes;
+ }
+ }
+ }
+ }
+
+ const uae_u32 form_size = (uae_u32)iff->size() - 8;
+ (*iff)[4] = form_size >> 24;
+ (*iff)[5] = form_size >> 16;
+ (*iff)[6] = form_size >> 8;
+ (*iff)[7] = form_size;
+ if (form_size & 1) {
+ iff->push_back(0);
+ }
+ return true;
+}
+
+static void to_amiga_start(void)
+{
+ to_amiga_phase = 0;
+ if (!initialized || !clipboard_data || to_amiga.empty()) {
+ return;
+ }
+ to_amiga_phase = 1;
+}
+
+static void queue_host_text_to_amiga(const std::string &text)
+{
+ to_amiga = make_iff_text(text);
+ to_amiga_start();
+}
+
+static void queue_host_bitmap_to_amiga(const UnixClipboardBitmap &bitmap, bool initial)
+{
+ if (make_iff_ilbm(bitmap, initial, &to_amiga)) {
+ to_amiga_start();
+ }
+}
+
+static int parse_csi(const std::string &text, size_t offset)
+{
+ while (offset < text.size()) {
+ if ((uae_u8)text[offset] >= 0x40) {
+ break;
+ }
+ offset++;
+ }
+ return (int)offset;
+}
+
+static std::string amiga_text_to_host_text(const std::string &text)
+{
+ std::string converted;
+ converted.reserve(text.size());
+ for (size_t i = 0; i < text.size(); i++) {
+ uae_u8 c = (uae_u8)text[i];
+ if (c == 0 && i + 1 < text.size()) {
+ continue;
+ }
+ if (c == 0x9b) {
+ i = parse_csi(text, i + 1);
+ continue;
+ }
+ if (c == 0x1b && i + 1 < text.size() && text[i + 1] == '[') {
+ i = parse_csi(text, i + 2);
+ continue;
+ }
+ if (c == '\r') {
+ converted.push_back('\n');
+ if (i + 1 < text.size() && text[i + 1] == '\n') {
+ i++;
+ }
+ continue;
+ }
+ converted.push_back((char)c);
+ }
+ return converted;
+}
+
+static void clipboard_put_text(const std::string &text)
+{
+ if (!write_host_clipboard_text(text)) {
+ write_log(_T("clipboard: failed to write host clipboard text\n"));
+ }
+}
+
+static void from_iff_text(const uae_u8 *addr, uae_u32 len)
+{
+ if (len < 12 || memcmp(addr, "FORM", 4) || memcmp(addr + 8, "FTXT", 4)) {
+ return;
+ }
+
+ std::string text;
+ uae_u32 offset = 12;
+ while (offset + 8 <= len) {
+ const uae_u8 *chunk = addr + offset;
+ uae_u32 csize = ((uae_u32)chunk[4] << 24) | ((uae_u32)chunk[5] << 16) | ((uae_u32)chunk[6] << 8) | chunk[7];
+ offset += 8;
+ if (csize > len - offset) {
+ break;
+ }
+ if (!memcmp(chunk, "CHRS", 4)) {
+ text.append((const char *)(addr + offset), csize);
+ }
+ offset += csize + (csize & 1);
+ }
+
+ clipboard_put_text(amiga_text_to_host_text(text));
+}
+
+static bool decompress_byterun1(const uae_u8 *src, const uae_u8 *end, std::vector<uae_u8> *out, size_t expected)
+{
+ out->clear();
+ out->reserve(expected);
+ while (src < end && out->size() < expected) {
+ const int8_t control = (int8_t)*src++;
+ if (control >= 0) {
+ const int count = control + 1;
+ if (src + count > end) {
+ return false;
+ }
+ const size_t writable = std::min((size_t)count, expected - out->size());
+ out->insert(out->end(), src, src + writable);
+ src += count;
+ } else if (control >= -127) {
+ if (src >= end) {
+ return false;
+ }
+ const int count = -control + 1;
+ const uae_u8 value = *src++;
+ const size_t writable = std::min((size_t)count, expected - out->size());
+ out->insert(out->end(), writable, value);
+ }
+ }
+ return out->size() == expected;
+}
+
+static bool parse_iff_ilbm(const uae_u8 *addr, uae_u32 len, UnixClipboardBitmap *bitmap)
+{
+ if (!bitmap || len < 12 || memcmp(addr, "FORM", 4) || memcmp(addr + 8, "ILBM", 4)) {
+ return false;
+ }
+ if (read_be32(addr + 4) > 0xffffff) {
+ return false;
+ }
+
+ int width = 0;
+ int height = 0;
+ int planes = 0;
+ int masking = 0;
+ int compression = 0;
+ uae_u32 camg = 0;
+ uae_u32 palette[256];
+ for (int i = 0; i < 256; i++) {
+ palette[i] = ((uae_u32)i << 16) | ((uae_u32)i << 8) | i;
+ }
+ bool have_bmhd = false;
+ const uae_u8 *body = NULL;
+ uae_u32 body_size = 0;
+
+ uae_u32 offset = 12;
+ while (offset + 8 <= len) {
+ const uae_u8 *chunk = addr + offset;
+ const uae_u32 csize = read_be32(chunk + 4);
+ offset += 8;
+ if (csize > len - offset) {
+ return false;
+ }
+ const uae_u8 *data = addr + offset;
+ if (!memcmp(chunk, "BMHD", 4) && csize >= 20) {
+ width = read_be16(data);
+ height = read_be16(data + 2);
+ planes = data[8];
+ masking = data[9];
+ compression = data[10];
+ have_bmhd = true;
+ } else if (!memcmp(chunk, "CAMG", 4) && csize >= 4) {
+ camg = read_be32(data);
+ if ((camg & 0xffff0000) && !(camg & 0x1000)) {
+ camg = 0;
+ }
+ } else if (!memcmp(chunk, "CMAP", 4)) {
+ const int colors = std::min<int>(256, csize / 3);
+ bool zero4 = true;
+ for (int i = 0; i < colors; i++) {
+ const uae_u8 r = data[(size_t)i * 3 + 0];
+ const uae_u8 g = data[(size_t)i * 3 + 1];
+ const uae_u8 b = data[(size_t)i * 3 + 2];
+ if ((r & 0x0f) || (g & 0x0f) || (b & 0x0f)) {
+ zero4 = false;
+ }
+ palette[i] = ((uae_u32)r << 16) | ((uae_u32)g << 8) | b;
+ }
+ if (zero4) {
+ for (int i = 0; i < colors; i++) {
+ const uae_u8 r = ((palette[i] >> 16) & 0xff) | ((palette[i] >> 20) & 0x0f);
+ const uae_u8 g = ((palette[i] >> 8) & 0xff) | ((palette[i] >> 12) & 0x0f);
+ const uae_u8 b = (palette[i] & 0xff) | ((palette[i] >> 4) & 0x0f);
+ palette[i] = ((uae_u32)r << 16) | ((uae_u32)g << 8) | b;
+ }
+ }
+ } else if (!memcmp(chunk, "BODY", 4)) {
+ body = data;
+ body_size = csize;
+ }
+ offset += csize + (csize & 1);
+ }
+
+ if (!have_bmhd || !body || width <= 0 || height <= 0 || planes <= 0 || planes > 24) {
+ return false;
+ }
+ if (planes > 8 && planes != 24 && !((camg & 0x0800) && planes > 4)) {
+ return false;
+ }
+
+ const int plane_rowbytes = ((width + 15) & ~15) / 8;
+ const int stored_planes = planes + (masking == 1 ? 1 : 0);
+ const size_t decoded_size = (size_t)plane_rowbytes * (size_t)height * (size_t)stored_planes;
+ std::vector<uae_u8> decoded;
+ if (compression) {
+ if (!decompress_byterun1(body, body + body_size, &decoded, decoded_size)) {
+ return false;
+ }
+ body = decoded.data();
+ body_size = (uae_u32)decoded.size();
+ } else if (body_size < decoded_size) {
+ return false;
+ }
+
+ const bool ham = (camg & 0x0800) && planes > 4;
+ const bool ehb = !(camg & (0x0800 | 0x0400 | 0x8000 | 0x0040)) && (camg & 0x0080) && planes == 6;
+ bitmap->width = width;
+ bitmap->height = height;
+ bitmap->pixels.assign((size_t)width * (size_t)height, 0);
+
+ for (int y = 0; y < height; y++) {
+ const uae_u8 *row = body + (size_t)y * (size_t)stored_planes * (size_t)plane_rowbytes;
+ uae_u32 ham_lastcolor = 0;
+ for (int x = 0; x < width; x++) {
+ int value = 0;
+ for (int b = 0; b < planes; b++) {
+ const int off = x / 8;
+ const int mask = 1 << (7 - (x & 7));
+ if (row[(size_t)b * plane_rowbytes + off] & mask) {
+ value |= 1 << b;
+ }
+ }
+ uae_u32 pixel = 0;
+ if (ham) {
+ if (planes >= 7) {
+ const uae_u32 c = value & 0x3f;
+ switch (value >> 6)
+ {
+ case 0: ham_lastcolor = palette[value & 0x3f]; break;
+ case 1: ham_lastcolor = (ham_lastcolor & 0xffff00) | (c << 2); break;
+ case 2: ham_lastcolor = (ham_lastcolor & 0x00ffff) | (c << 18); break;
+ case 3: ham_lastcolor = (ham_lastcolor & 0xff00ff) | (c << 10); break;
+ }
+ } else {
+ const uae_u32 c = value & 0x0f;
+ switch (value >> 4)
+ {
+ case 0: ham_lastcolor = palette[value & 0x0f]; break;
+ case 1: ham_lastcolor = (ham_lastcolor & 0xffff00) | (c << 4); break;
+ case 2: ham_lastcolor = (ham_lastcolor & 0x00ffff) | (c << 20); break;
+ case 3: ham_lastcolor = (ham_lastcolor & 0xff00ff) | (c << 12); break;
+ }
+ }
+ pixel = ham_lastcolor;
+ } else if (planes <= 8) {
+ if (ehb && value >= 32) {
+ const uae_u32 base = palette[value - 32];
+ pixel = (((base >> 17) & 0x7f) << 16) | (((base >> 9) & 0x7f) << 8) | ((base >> 1) & 0x7f);
+ } else {
+ pixel = palette[value & 0xff];
+ }
+ } else {
+ uae_u8 r = 0, g = 0, b = 0;
+ for (int bit = 0; bit < 8; bit++) {
+ const int off = x / 8;
+ const int mask = 1 << (7 - (x & 7));
+ if (row[(size_t)bit * plane_rowbytes + off] & mask) {
+ r |= 1 << bit;
+ }
+ if (row[(size_t)(8 + bit) * plane_rowbytes + off] & mask) {
+ g |= 1 << bit;
+ }
+ if (row[(size_t)(16 + bit) * plane_rowbytes + off] & mask) {
+ b |= 1 << bit;
+ }
+ }
+ pixel = ((uae_u32)r << 16) | ((uae_u32)g << 8) | b;
+ }
+ bitmap->pixels[(size_t)y * (size_t)width + x] = pixel;
+ }
+ }
+ return true;
+}
+
+static void from_iff(TrapContext *ctx, uaecptr data, uae_u32 len)
+{
+ if (len < 12 || !trap_valid_address(ctx, data, len)) {
+ return;
+ }
+
+ std::vector<uae_u8> buffer((len + 3) & ~3);
+ trap_get_bytes(ctx, buffer.data(), data, (len + 3) & ~3);
+ if (!memcmp(buffer.data(), "FORM", 4) && !memcmp(buffer.data() + 8, "FTXT", 4)) {
+ from_iff_text(buffer.data(), len);
+ } else if (!memcmp(buffer.data(), "FORM", 4) && !memcmp(buffer.data() + 8, "ILBM", 4)) {
+#ifdef UAE_UNIX_WITH_SDL3_CLIPBOARD_DATA
+ UnixClipboardBitmap bitmap;
+ std::vector<uae_u8> bmp;
+ if (parse_iff_ilbm(buffer.data(), len, &bitmap) && make_bmp(bitmap, &bmp)) {
+ std::vector<uae_u8> png;
+#ifdef UAE_UNIX_CLIPBOARD_PNG
+ make_png(bitmap, &png);
+#endif
+ std::vector<uae_u8> tiff;
+#ifdef UAE_UNIX_CLIPBOARD_IMAGEIO
+ make_tiff(bitmap, &tiff);
+#endif
+ write_host_clipboard_image(bmp, png.empty() ? NULL : &png, tiff.empty() ? NULL : &tiff);
+ }
+#endif
+ }
+}
+
+static void clipboard_read_host(TrapContext *, bool keyboardinject, bool initial)
+{
+ if (clip_disabled || (!keyboardinject && to_amiga_phase)) {
+ return;
+ }
+
+ std::string text;
+ if (read_host_clipboard_text(&text, initial ? CLIP_SIZE_LIMIT_INIT : CLIP_SIZE_LIMIT)) {
+ if (keyboardinject) {
+ normalize_text_for_keybuf(&text);
+ if (!text.empty()) {
+ keybuf_inject(text.c_str());
+ }
+ return;
+ }
+
+ if (last_host_clipboard_valid && text == last_host_clipboard) {
+ return;
+ }
+ last_host_clipboard = text;
+ last_host_clipboard_valid = true;
+ last_host_clipboard_bitmap.clear();
+ last_host_clipboard_bitmap_valid = false;
+ queue_host_text_to_amiga(text);
+ return;
+ }
+
+#ifdef UAE_UNIX_WITH_SDL3_CLIPBOARD_DATA
+ if (!keyboardinject) {
+ UnixClipboardBitmap bitmap;
+ std::vector<uae_u8> image;
+#ifdef UAE_UNIX_CLIPBOARD_PNG
+ if (read_host_clipboard_png(&image, initial ? CLIP_SIZE_LIMIT_INIT : CLIP_SIZE_LIMIT) && parse_png(image, &bitmap)) {
+ if (last_host_clipboard_bitmap_valid && image == last_host_clipboard_bitmap) {
+ return;
+ }
+ last_host_clipboard.clear();
+ last_host_clipboard_valid = false;
+ last_host_clipboard_bitmap = image;
+ last_host_clipboard_bitmap_valid = true;
+ queue_host_bitmap_to_amiga(bitmap, initial);
+ return;
+ }
+#endif
+#ifdef UAE_UNIX_CLIPBOARD_IMAGEIO
+ if (read_host_clipboard_tiff(&image, initial ? CLIP_SIZE_LIMIT_INIT : CLIP_SIZE_LIMIT) && parse_imageio_bitmap(image, &bitmap)) {
+ if (last_host_clipboard_bitmap_valid && image == last_host_clipboard_bitmap) {
+ return;
+ }
+ last_host_clipboard.clear();
+ last_host_clipboard_valid = false;
+ last_host_clipboard_bitmap = image;
+ last_host_clipboard_bitmap_valid = true;
+ queue_host_bitmap_to_amiga(bitmap, initial);
+ return;
+ }
+#endif
+ if (read_host_clipboard_bmp(&image, initial ? CLIP_SIZE_LIMIT_INIT : CLIP_SIZE_LIMIT) && parse_bmp(image, &bitmap)) {
+ if (last_host_clipboard_bitmap_valid && image == last_host_clipboard_bitmap) {
+ return;
+ }
+ last_host_clipboard.clear();
+ last_host_clipboard_valid = false;
+ last_host_clipboard_bitmap = image;
+ last_host_clipboard_bitmap_valid = true;
+ queue_host_bitmap_to_amiga(bitmap, initial);
+ }
+ }
+#endif
+}
+
+static uae_u32 to_amiga_start_cb(TrapContext *ctx, void *)
+{
+ if (!clipboard_data || to_amiga.empty() || trap_get_long(ctx, clipboard_data) != 0) {
+ return 0;
+ }
+ trap_put_long(ctx, clipboard_data, (uae_u32)to_amiga.size());
+ uae_Signal(trap_get_long(ctx, clipboard_data + 8), 1 << 13);
+ to_amiga_phase = 2;
+ return 1;
+}
+
+static uae_u32 clipboard_vsync_cb(TrapContext *ctx, void *)
+{
+ if (clipboard_data) {
+ uaecptr task = trap_get_long(ctx, clipboard_data + 8);
+ if (task && native2amiga_isfree()) {
+ uae_Signal(task, 1 << 13);
+ }
+ }
+ return 0;
+}
+
+void clipboard_vsync(void)
+{
+ if (!filesys_heartbeat() || !clipboard_data || !initialized) {
+ return;
+ }
+
+ if (signaling) {
+ vdelay--;
+ if (vdelay <= 0) {
+ trap_callback(clipboard_vsync_cb, NULL);
+ vdelay = 50;
+ }
+ }
+
+ if (vdelay2 > 0) {
+ vdelay2--;
+ }
+
+ if (to_amiga_phase == 1 && vdelay2 <= 0) {
+ trap_callback(to_amiga_start_cb, NULL);
+ }
+
+ if (host_poll_delay > 0) {
+ host_poll_delay--;
+ } else {
+ host_poll_delay = 100;
+ clipboard_read_host(NULL, false, false);
+ }
+}
+
+void clipboard_host_changed(void)
+{
+ host_poll_delay = 0;
+}
+
+void clipboard_unsafeperiod(void)
+{
+ vdelay2 = 100;
+ if (vdelay < 60) {
+ vdelay = 60;
+ }
+}
+
+void clipboard_disable(bool disabled)
+{
+ clip_disabled = disabled;
+}
+
+uaecptr amiga_clipboard_proc_start(TrapContext *)
+{
+ signaling = 1;
+ to_amiga_start();
+ return clipboard_data;
+}
+
+void amiga_clipboard_task_start(TrapContext *, uaecptr data)
+{
+ clipboard_data = data;
+ signaling = 1;
+ write_log(_T("clipboard task init: %08x\n"), clipboard_data);
+ to_amiga_start();
+}
+
+int amiga_clipboard_want_data(TrapContext *ctx)
+{
+ if (!clipboard_data) {
+ return 0;
+ }
+
+ uae_u32 addr = trap_get_long(ctx, clipboard_data + 4);
+ uae_u32 size = trap_get_long(ctx, clipboard_data);
+
+ if (!initialized || to_amiga.empty()) {
+ to_amiga.clear();
+ to_amiga_phase = 0;
+ return 0;
+ }
+ if (size != to_amiga.size()) {
+ write_log(_T("clipboard: size %d <> %d mismatch\n"), size, (int)to_amiga.size());
+ to_amiga.clear();
+ to_amiga_phase = 0;
+ return 0;
+ }
+ if (addr && size) {
+ trap_put_bytes(ctx, to_amiga.data(), addr, size);
+ }
+ to_amiga.clear();
+ to_amiga_phase = 0;
+ return 1;
+}
+
+void amiga_clipboard_got_data(TrapContext *ctx, uaecptr data, uae_u32, uae_u32 actual)
+{
+ if (!initialized) {
+ return;
+ }
+ from_iff(ctx, data, actual);
+}
+
+void amiga_clipboard_die(TrapContext *)
+{
+ signaling = 0;
+ write_log(_T("clipboard not initialized\n"));
+}
+
+void amiga_clipboard_init(TrapContext *ctx)
+{
+ signaling = 0;
+ initialized = 1;
+ host_poll_delay = 50;
+ write_log(_T("clipboard initialized\n"));
+ clipboard_read_host(ctx, false, true);
+}
+
+void target_paste_to_keyboard(void)
+{
+ clipboard_read_host(NULL, true, false);
+}
--- /dev/null
+#include "sysconfig.h"
+#include "sysdeps.h"
+
+#include "uae/dlopen.h"
+#include "uae/log.h"
+#include "target_dlopen.h"
+
+#include <dlfcn.h>
+#if defined(UAE_HOST_DARWIN)
+#include <mach-o/dyld.h>
+#elif defined(UAE_HOST_LINUX)
+#include <unistd.h>
+#endif
+
+static bool get_executable_dir(TCHAR *out, size_t out_size)
+{
+#if defined(UAE_HOST_DARWIN)
+ uint32_t size = out_size;
+ if (_NSGetExecutablePath(out, &size) != 0) {
+ return false;
+ }
+#elif defined(UAE_HOST_LINUX)
+ ssize_t len = readlink("/proc/self/exe", out, out_size - 1);
+ if (len <= 0 || (size_t)len >= out_size) {
+ return false;
+ }
+ out[len] = 0;
+#else
+ return false;
+#endif
+ TCHAR *slash = _tcsrchr(out, FSDB_DIR_SEPARATOR);
+ if (!slash) {
+ return false;
+ }
+ *slash = 0;
+ return true;
+}
+
+static bool append_plugin_path(TCHAR *path, size_t path_size,
+ const TCHAR *prefix, const TCHAR *name, const TCHAR *suffix)
+{
+ if (_tcslen(prefix) + _tcslen(name) + _tcslen(suffix) >= path_size) {
+ return false;
+ }
+ _tcscpy(path, prefix);
+ _tcscat(path, name);
+ _tcscat(path, suffix);
+ return true;
+}
+
+static UAE_DLHANDLE try_unix_plugin_path(const TCHAR *path,
+ const char **last_error)
+{
+ dlerror();
+ UAE_DLHANDLE handle = dlopen(path, RTLD_NOW);
+ if (!handle && last_error) {
+ *last_error = dlerror();
+ }
+ return handle;
+}
+
+bool target_dlopen_plugin(const TCHAR *name, TCHAR *loaded_path,
+ size_t loaded_path_size, UAE_DLHANDLE *handlep)
+{
+ const char *last_error = NULL;
+ TCHAR executable_dir[MAX_DPATH] = { 0 };
+ bool have_executable_dir = get_executable_dir(executable_dir,
+ MAX_DPATH);
+
+ *handlep = NULL;
+
+#if defined(UAE_HOST_DARWIN)
+ const TCHAR *suffixes[] = { _T(".so"), LT_MODULE_EXT, NULL };
+ const TCHAR *prefixes[] = {
+ _T("@executable_path/../PlugIns/"),
+ _T("@executable_path/"),
+ _T("./plugins/"),
+ _T("./"),
+ _T(""),
+ NULL
+ };
+#else
+ const TCHAR *suffixes[] = { LT_MODULE_EXT, NULL };
+ const TCHAR *prefixes[] = {
+ _T("./plugins/"),
+ _T("./"),
+ _T(""),
+ NULL
+ };
+#endif
+
+ for (int s = 0; suffixes[s]; s++) {
+ for (int p = 0; prefixes[p]; p++) {
+ TCHAR path[MAX_DPATH];
+ if (!append_plugin_path(path, MAX_DPATH, prefixes[p], name,
+ suffixes[s])) {
+ continue;
+ }
+ UAE_DLHANDLE handle = try_unix_plugin_path(path, &last_error);
+ if (handle) {
+ _tcsncpy(loaded_path, path, loaded_path_size - 1);
+ loaded_path[loaded_path_size - 1] = 0;
+ *handlep = handle;
+ return true;
+ }
+ }
+ if (have_executable_dir) {
+ TCHAR prefix[MAX_DPATH];
+ if (_tcslen(executable_dir) + 2 < MAX_DPATH) {
+ _tcscpy(prefix, executable_dir);
+ _tcscat(prefix, _T("/"));
+ TCHAR path[MAX_DPATH];
+ if (append_plugin_path(path, MAX_DPATH, prefix, name,
+ suffixes[s])) {
+ UAE_DLHANDLE handle = try_unix_plugin_path(path,
+ &last_error);
+ if (handle) {
+ _tcsncpy(loaded_path, path,
+ loaded_path_size - 1);
+ loaded_path[loaded_path_size - 1] = 0;
+ *handlep = handle;
+ return true;
+ }
+ }
+ }
+ if (_tcslen(executable_dir) + 10 < MAX_DPATH) {
+ _tcscpy(prefix, executable_dir);
+ _tcscat(prefix, _T("/plugins/"));
+ TCHAR path[MAX_DPATH];
+ if (append_plugin_path(path, MAX_DPATH, prefix, name,
+ suffixes[s])) {
+ UAE_DLHANDLE handle = try_unix_plugin_path(path,
+ &last_error);
+ if (handle) {
+ _tcsncpy(loaded_path, path,
+ loaded_path_size - 1);
+ loaded_path[loaded_path_size - 1] = 0;
+ *handlep = handle;
+ return true;
+ }
+ }
+ }
+#ifdef WINUAE_UNIX_INSTALL_PLUGINDIR_RELATIVE
+ if (_tcslen(executable_dir) + 4 +
+ _tcslen(WINUAE_UNIX_INSTALL_PLUGINDIR_RELATIVE) + 2 <
+ MAX_DPATH) {
+ _tcscpy(prefix, executable_dir);
+ _tcscat(prefix, _T("/../"));
+ _tcscat(prefix, WINUAE_UNIX_INSTALL_PLUGINDIR_RELATIVE);
+ _tcscat(prefix, _T("/"));
+ TCHAR path[MAX_DPATH];
+ if (append_plugin_path(path, MAX_DPATH, prefix, name,
+ suffixes[s])) {
+ UAE_DLHANDLE handle = try_unix_plugin_path(path,
+ &last_error);
+ if (handle) {
+ _tcsncpy(loaded_path, path,
+ loaded_path_size - 1);
+ loaded_path[loaded_path_size - 1] = 0;
+ *handlep = handle;
+ return true;
+ }
+ }
+ }
+#endif
+ }
+ }
+
+ if (last_error) {
+ write_log("DLOPEN: %s\n", last_error);
+ }
+ write_log(_T("DLOPEN: Could not find plugin \"%s\"\n"), name);
+ return true;
+}
--- /dev/null
+#include "sysconfig.h"
+#include "sysdeps.h"
+
+#ifdef WITH_MIDI
+
+#include <deque>
+#include <mutex>
+#include <vector>
+
+#if defined(WINUAE_UNIX_WITH_COREMIDI)
+#include <CoreFoundation/CoreFoundation.h>
+#include <CoreMIDI/CoreMIDI.h>
+#elif defined(WINUAE_UNIX_WITH_ALSA_MIDI)
+#include <alsa/asoundlib.h>
+#endif
+
+#include "options.h"
+#include "midi.h"
+#ifdef WITH_MIDIEMU
+#include "midiemu.h"
+#endif
+
+extern int serdev;
+
+BOOL midi_ready = FALSE;
+
+struct unix_midi_output_device {
+ int devid;
+ TCHAR name[256];
+ TCHAR label[256];
+ bool emulated;
+#if defined(WINUAE_UNIX_WITH_COREMIDI)
+ MIDIEndpointRef endpoint;
+#elif defined(WINUAE_UNIX_WITH_ALSA_MIDI)
+ int client;
+ int port;
+#endif
+};
+
+static std::vector<unix_midi_output_device> midi_outputs;
+static std::vector<unix_midi_output_device> midi_inputs;
+static bool midi_outputs_enumerated;
+static bool midi_inputs_enumerated;
+static MidiOutStatus out_status;
+static std::vector<uae_u8> sysex_buffer;
+static std::deque<uae_u8> input_queue;
+static std::mutex input_queue_mutex;
+
+#if defined(WINUAE_UNIX_WITH_COREMIDI)
+static MIDIClientRef midi_client;
+static MIDIPortRef midi_out_port;
+static MIDIEndpointRef midi_out_endpoint;
+static MIDIPortRef midi_in_port;
+static MIDIEndpointRef midi_in_endpoint;
+#elif defined(WINUAE_UNIX_WITH_ALSA_MIDI)
+static snd_seq_t *alsa_seq;
+static int alsa_out_port = -1;
+static int alsa_in_port = -1;
+static snd_midi_event_t *alsa_encoder;
+static snd_midi_event_t *alsa_decoder;
+#endif
+static bool midi_in_ready;
+
+static const uae_u8 plen[128] = {
+ 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+ 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+ 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+ 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+ 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+ 1,1,2,1,0,0,0,0,0,0,0,0,0,0,0,0
+};
+
+static void midi_reset_parser(void)
+{
+ memset(&out_status, 0, sizeof out_status);
+ sysex_buffer.clear();
+}
+
+static void midi_clear_input_queue(void)
+{
+ std::lock_guard<std::mutex> lock(input_queue_mutex);
+ input_queue.clear();
+}
+
+static void enqueue_midi_input_bytes(const uae_u8 *data, int len)
+{
+ if (!data || len <= 0) {
+ return;
+ }
+ std::lock_guard<std::mutex> lock(input_queue_mutex);
+ for (int i = 0; i < len; i++) {
+ if (input_queue.size() >= BUFFLEN) {
+ input_queue.pop_front();
+ }
+ input_queue.push_back(data[i]);
+ if (currprefs.win32_midirouter && midi_ready) {
+ BYTE out = data[i];
+ Midi_Parse(midi_output, &out);
+ }
+ }
+}
+
+#if defined(WINUAE_UNIX_WITH_COREMIDI)
+static void coremidi_input_proc(const MIDIPacketList *packet_list, void*, void*)
+{
+ const MIDIPacket *packet = &packet_list->packet[0];
+ for (UInt32 i = 0; i < packet_list->numPackets; i++) {
+ enqueue_midi_input_bytes(packet->data, packet->length);
+ packet = MIDIPacketNext(packet);
+ }
+}
+#endif
+
+#if defined(WINUAE_UNIX_WITH_COREMIDI)
+static bool coremidi_object_string(MIDIObjectRef object, CFStringRef property, TCHAR *out, size_t out_size)
+{
+ if (!out || out_size == 0) {
+ return false;
+ }
+ out[0] = 0;
+ CFStringRef str = NULL;
+ if (MIDIObjectGetStringProperty(object, property, &str) != noErr || !str) {
+ return false;
+ }
+ bool ok = CFStringGetCString(str, out, out_size, kCFStringEncodingUTF8);
+ CFRelease(str);
+ return ok && out[0];
+}
+#endif
+
+static void enumerate_midi_outputs(void)
+{
+ midi_outputs.clear();
+ midi_outputs_enumerated = true;
+#if defined(WINUAE_UNIX_WITH_COREMIDI)
+ const ItemCount count = MIDIGetNumberOfDestinations();
+ for (ItemCount i = 0; i < count; i++) {
+ MIDIEndpointRef endpoint = MIDIGetDestination(i);
+ if (!endpoint) {
+ continue;
+ }
+ unix_midi_output_device dev;
+ memset(&dev, 0, sizeof dev);
+ dev.devid = (int)i;
+ dev.endpoint = endpoint;
+ if (!coremidi_object_string(endpoint, kMIDIPropertyDisplayName, dev.name, sizeof dev.name / sizeof(TCHAR))
+ && !coremidi_object_string(endpoint, kMIDIPropertyName, dev.name, sizeof dev.name / sizeof(TCHAR))) {
+ _sntprintf(dev.name, sizeof dev.name / sizeof(TCHAR), _T("CoreMIDI destination %d"), (int)i + 1);
+ }
+ midi_outputs.push_back(dev);
+ }
+#elif defined(WINUAE_UNIX_WITH_ALSA_MIDI)
+ snd_seq_t *seq = NULL;
+ if (snd_seq_open(&seq, "default", SND_SEQ_OPEN_OUTPUT, 0) < 0) {
+ return;
+ }
+ snd_seq_client_info_t *client_info;
+ snd_seq_port_info_t *port_info;
+ snd_seq_client_info_alloca(&client_info);
+ snd_seq_port_info_alloca(&port_info);
+ snd_seq_client_info_set_client(client_info, -1);
+ int devid = 0;
+ while (snd_seq_query_next_client(seq, client_info) >= 0) {
+ int client = snd_seq_client_info_get_client(client_info);
+ snd_seq_port_info_set_client(port_info, client);
+ snd_seq_port_info_set_port(port_info, -1);
+ while (snd_seq_query_next_port(seq, port_info) >= 0) {
+ unsigned int caps = snd_seq_port_info_get_capability(port_info);
+ if ((caps & SND_SEQ_PORT_CAP_WRITE) == 0 || (caps & SND_SEQ_PORT_CAP_SUBS_WRITE) == 0) {
+ continue;
+ }
+ unix_midi_output_device dev;
+ memset(&dev, 0, sizeof dev);
+ dev.devid = devid++;
+ dev.client = client;
+ dev.port = snd_seq_port_info_get_port(port_info);
+ const char *client_name = snd_seq_client_info_get_name(client_info);
+ const char *port_name = snd_seq_port_info_get_name(port_info);
+ _sntprintf(dev.name, sizeof dev.name / sizeof(TCHAR), _T("%d:%d %s%s%s"),
+ dev.client, dev.port,
+ client_name ? client_name : "ALSA",
+ port_name && port_name[0] ? " " : "",
+ port_name ? port_name : "");
+ midi_outputs.push_back(dev);
+ }
+ }
+ snd_seq_close(seq);
+#endif
+#ifdef WITH_MIDIEMU
+ unix_midi_output_device dev;
+ memset(&dev, 0, sizeof dev);
+ dev.devid = (int)midi_outputs.size();
+ dev.emulated = true;
+ _tcscpy(dev.name, _T("Munt MT-32"));
+ _tcscpy(dev.label, midi_emu_available(_T("MT-32")) ? _T("Munt MT-32") : _T("Munt MT-32 (Missing ROMs)"));
+ midi_outputs.push_back(dev);
+
+ memset(&dev, 0, sizeof dev);
+ dev.devid = (int)midi_outputs.size();
+ dev.emulated = true;
+ _tcscpy(dev.name, _T("Munt CM-32L"));
+ _tcscpy(dev.label, midi_emu_available(_T("CM-32L")) ? _T("Munt CM-32L") : _T("Munt CM-32L (Missing ROMs)"));
+ midi_outputs.push_back(dev);
+#endif
+}
+
+static void enumerate_midi_inputs(void)
+{
+ midi_inputs.clear();
+ midi_inputs_enumerated = true;
+#if defined(WINUAE_UNIX_WITH_COREMIDI)
+ const ItemCount count = MIDIGetNumberOfSources();
+ for (ItemCount i = 0; i < count; i++) {
+ MIDIEndpointRef endpoint = MIDIGetSource(i);
+ if (!endpoint) {
+ continue;
+ }
+ unix_midi_output_device dev;
+ memset(&dev, 0, sizeof dev);
+ dev.devid = (int)i;
+ dev.endpoint = endpoint;
+ if (!coremidi_object_string(endpoint, kMIDIPropertyDisplayName, dev.name, sizeof dev.name / sizeof(TCHAR))
+ && !coremidi_object_string(endpoint, kMIDIPropertyName, dev.name, sizeof dev.name / sizeof(TCHAR))) {
+ _sntprintf(dev.name, sizeof dev.name / sizeof(TCHAR), _T("CoreMIDI source %d"), (int)i + 1);
+ }
+ midi_inputs.push_back(dev);
+ }
+#elif defined(WINUAE_UNIX_WITH_ALSA_MIDI)
+ snd_seq_t *seq = NULL;
+ if (snd_seq_open(&seq, "default", SND_SEQ_OPEN_INPUT, 0) < 0) {
+ return;
+ }
+ snd_seq_client_info_t *client_info;
+ snd_seq_port_info_t *port_info;
+ snd_seq_client_info_alloca(&client_info);
+ snd_seq_port_info_alloca(&port_info);
+ snd_seq_client_info_set_client(client_info, -1);
+ int devid = 0;
+ while (snd_seq_query_next_client(seq, client_info) >= 0) {
+ int client = snd_seq_client_info_get_client(client_info);
+ snd_seq_port_info_set_client(port_info, client);
+ snd_seq_port_info_set_port(port_info, -1);
+ while (snd_seq_query_next_port(seq, port_info) >= 0) {
+ unsigned int caps = snd_seq_port_info_get_capability(port_info);
+ if ((caps & SND_SEQ_PORT_CAP_READ) == 0 || (caps & SND_SEQ_PORT_CAP_SUBS_READ) == 0) {
+ continue;
+ }
+ unix_midi_output_device dev;
+ memset(&dev, 0, sizeof dev);
+ dev.devid = devid++;
+ dev.client = client;
+ dev.port = snd_seq_port_info_get_port(port_info);
+ const char *client_name = snd_seq_client_info_get_name(client_info);
+ const char *port_name = snd_seq_port_info_get_name(port_info);
+ _sntprintf(dev.name, sizeof dev.name / sizeof(TCHAR), _T("%d:%d %s%s%s"),
+ dev.client, dev.port,
+ client_name ? client_name : "ALSA",
+ port_name && port_name[0] ? " " : "",
+ port_name ? port_name : "");
+ midi_inputs.push_back(dev);
+ }
+ }
+ snd_seq_close(seq);
+#endif
+}
+
+static void ensure_midi_outputs(void)
+{
+ if (!midi_outputs_enumerated) {
+ enumerate_midi_outputs();
+ }
+}
+
+static void ensure_midi_inputs(void)
+{
+ if (!midi_inputs_enumerated) {
+ enumerate_midi_inputs();
+ }
+}
+
+static unix_midi_output_device *find_midi_device(std::vector<unix_midi_output_device> &devices, int devid, bool default_to_first)
+{
+ if (devices.empty()) {
+ return NULL;
+ }
+ if (default_to_first && devid == -1) {
+ for (size_t i = 0; i < devices.size(); i++) {
+ if (!devices[i].emulated) {
+ return &devices[i];
+ }
+ }
+ return NULL;
+ }
+ for (size_t i = 0; i < devices.size(); i++) {
+ if (devices[i].devid == devid) {
+ return &devices[i];
+ }
+ }
+ return NULL;
+}
+
+static unix_midi_output_device *find_midi_output(int devid)
+{
+ ensure_midi_outputs();
+ return find_midi_device(midi_outputs, devid, true);
+}
+
+static unix_midi_output_device *find_midi_input(int devid)
+{
+ ensure_midi_inputs();
+ return find_midi_device(midi_inputs, devid, false);
+}
+
+static bool has_native_midi_output(void)
+{
+ ensure_midi_outputs();
+ for (size_t i = 0; i < midi_outputs.size(); i++) {
+ if (!midi_outputs[i].emulated) {
+ return true;
+ }
+ }
+ return false;
+}
+
+int unix_midi_output_device_count(void)
+{
+ ensure_midi_outputs();
+ return (int)midi_outputs.size() + (has_native_midi_output() ? 1 : 0);
+}
+
+int unix_midi_output_device_id(int index)
+{
+ ensure_midi_outputs();
+ const bool has_default = has_native_midi_output();
+ if (index == 0 && has_default) {
+ return -1;
+ }
+ if (has_default) {
+ index--;
+ }
+ if (index < 0 || index >= (int)midi_outputs.size()) {
+ return -2;
+ }
+ return midi_outputs[index].devid;
+}
+
+const TCHAR *unix_midi_output_device_display_name(int index)
+{
+ static TCHAR name[320];
+ ensure_midi_outputs();
+ const bool has_default = has_native_midi_output();
+ if (index == 0 && has_default) {
+ return _T("Default MIDI-Out Device");
+ }
+ if (has_default) {
+ index--;
+ }
+ if (index < 0 || index >= (int)midi_outputs.size()) {
+ return _T("");
+ }
+ _sntprintf(name, sizeof name / sizeof(TCHAR), _T("%s"),
+ midi_outputs[index].label[0] ? midi_outputs[index].label : midi_outputs[index].name);
+ return name;
+}
+
+const TCHAR *unix_midi_output_device_config_name_for_id(int devid)
+{
+ if (devid < -1) {
+ return _T("none");
+ }
+ if (devid == -1) {
+ return _T("default");
+ }
+ unix_midi_output_device *dev = find_midi_output(devid);
+ return dev ? dev->name : _T("default");
+}
+
+int unix_midi_output_device_id_from_config_name(const TCHAR *name)
+{
+ if (!name || !name[0] || !_tcsicmp(name, _T("none"))) {
+ return -2;
+ }
+ if (!_tcsicmp(name, _T("default"))) {
+ return -1;
+ }
+ ensure_midi_outputs();
+ for (size_t i = 0; i < midi_outputs.size(); i++) {
+ if (!_tcsicmp(name, midi_outputs[i].name)) {
+ return midi_outputs[i].devid;
+ }
+ }
+ return -2;
+}
+
+int unix_midi_input_device_count(void)
+{
+ ensure_midi_inputs();
+ return (int)midi_inputs.size();
+}
+
+int unix_midi_input_device_id(int index)
+{
+ ensure_midi_inputs();
+ if (index < 0 || index >= (int)midi_inputs.size()) {
+ return -1;
+ }
+ return midi_inputs[index].devid;
+}
+
+const TCHAR *unix_midi_input_device_display_name(int index)
+{
+ static TCHAR name[320];
+ ensure_midi_inputs();
+ if (index < 0 || index >= (int)midi_inputs.size()) {
+ return _T("");
+ }
+ _sntprintf(name, sizeof name / sizeof(TCHAR), _T("%s"), midi_inputs[index].name);
+ return name;
+}
+
+const TCHAR *unix_midi_input_device_config_name_for_id(int devid)
+{
+ if (devid < 0) {
+ return _T("none");
+ }
+ unix_midi_output_device *dev = find_midi_input(devid);
+ return dev ? dev->name : _T("none");
+}
+
+int unix_midi_input_device_id_from_config_name(const TCHAR *name)
+{
+ if (!name || !name[0] || !_tcsicmp(name, _T("none"))) {
+ return -1;
+ }
+ ensure_midi_inputs();
+ for (size_t i = 0; i < midi_inputs.size(); i++) {
+ if (!_tcsicmp(name, midi_inputs[i].name)) {
+ return midi_inputs[i].devid;
+ }
+ }
+ return -1;
+}
+
+static bool send_midi_bytes(const uae_u8 *data, int len)
+{
+ if (!data || len <= 0 || !midi_ready) {
+ return false;
+ }
+#if defined(WINUAE_UNIX_WITH_COREMIDI)
+ std::vector<Byte> packet_storage(sizeof(MIDIPacketList) + len + 128);
+ MIDIPacketList *packet_list = (MIDIPacketList*)packet_storage.data();
+ MIDIPacket *packet = MIDIPacketListInit(packet_list);
+ packet = MIDIPacketListAdd(packet_list, packet_storage.size(), packet, 0, len, data);
+ if (!packet) {
+ return false;
+ }
+ return MIDISend(midi_out_port, midi_out_endpoint, packet_list) == noErr;
+#elif defined(WINUAE_UNIX_WITH_ALSA_MIDI)
+ bool sent = false;
+ for (int i = 0; i < len; i++) {
+ snd_seq_event_t ev;
+ snd_seq_ev_clear(&ev);
+ int ret = snd_midi_event_encode_byte(alsa_encoder, data[i], &ev);
+ if (ret > 0) {
+ snd_seq_ev_set_source(&ev, alsa_out_port);
+ snd_seq_ev_set_subs(&ev);
+ snd_seq_ev_set_direct(&ev);
+ if (snd_seq_event_output_direct(alsa_seq, &ev) >= 0) {
+ sent = true;
+ }
+ }
+ }
+ return sent;
+#else
+ return false;
+#endif
+}
+
+static uae_u8 midi_scale_volume(uae_u8 value)
+{
+ int volume = currprefs.sound_volume_midi;
+ if (volume <= 0) {
+ return value;
+ }
+ if (volume >= 100) {
+ return 0;
+ }
+ return (uae_u8)((int)value * (100 - volume) / 100);
+}
+
+static void midi_apply_output_volume(uae_u8 *msg, int len)
+{
+ if (!msg || len < 2 || currprefs.sound_volume_midi <= 0) {
+ return;
+ }
+ switch (msg[0] & 0xf0)
+ {
+ case 0x80:
+ case 0x90:
+ case 0xa0:
+ if (len >= 3) {
+ msg[2] = midi_scale_volume(msg[2]);
+ }
+ break;
+ case 0xd0:
+ msg[1] = midi_scale_volume(msg[1]);
+ break;
+ }
+}
+
+#if defined(WINUAE_UNIX_WITH_ALSA_MIDI)
+static void poll_alsa_midi_input(void)
+{
+ if (!midi_in_ready || !alsa_seq || !alsa_decoder) {
+ return;
+ }
+ while (snd_seq_event_input_pending(alsa_seq, 1) > 0) {
+ snd_seq_event_t *ev = NULL;
+ if (snd_seq_event_input(alsa_seq, &ev) < 0 || !ev) {
+ break;
+ }
+ uae_u8 data[256];
+ long len = snd_midi_event_decode(alsa_decoder, data, sizeof data, ev);
+ if (len > 0) {
+ enqueue_midi_input_bytes(data, (int)len);
+ }
+ }
+}
+#else
+static void poll_alsa_midi_input(void)
+{
+}
+#endif
+
+int Midi_Parse(midi_direction_e direction, BYTE *dataptr)
+{
+ if (direction != midi_output || !dataptr) {
+ return 0;
+ }
+ const uae_u8 data = (uae_u8)*dataptr;
+ if (data >= 0x80) {
+ if (data < MIDI_CLOCK && out_status.sysex) {
+ sysex_buffer.push_back(MIDI_EOX);
+ send_midi_bytes(sysex_buffer.data(), (int)sysex_buffer.size());
+ sysex_buffer.clear();
+ out_status.sysex = 0;
+ out_status.unknown = 1;
+ if (data == MIDI_EOX) {
+ return 0;
+ }
+ }
+ out_status.status = data;
+ out_status.length = plen[data & 0x7f];
+ out_status.posn = 0;
+ out_status.unknown = 0;
+ if (data == MIDI_SYSX) {
+ out_status.sysex = 1;
+ sysex_buffer.clear();
+ sysex_buffer.push_back(data);
+ return 0;
+ }
+ if (out_status.length == 0) {
+ send_midi_bytes(&data, 1);
+ }
+ return 0;
+ }
+ if (out_status.sysex) {
+ if (sysex_buffer.size() < BUFFLEN) {
+ sysex_buffer.push_back(data);
+ }
+ return 0;
+ }
+ if (out_status.unknown) {
+ return 0;
+ }
+ if (++out_status.posn == 1) {
+ out_status.byte1 = data;
+ } else {
+ out_status.byte2 = data;
+ }
+ if (out_status.posn >= out_status.length) {
+ uae_u8 msg[3] = {
+ (uae_u8)out_status.status,
+ (uae_u8)out_status.byte1,
+ (uae_u8)out_status.byte2
+ };
+ const int len = 1 + out_status.length;
+ out_status.posn = 0;
+ midi_apply_output_volume(msg, len);
+ send_midi_bytes(msg, len);
+ }
+ return 0;
+}
+
+int Midi_Open(void)
+{
+ if (midi_ready) {
+ return 1;
+ }
+ if (currprefs.win32_midioutdev < -1) {
+ return 0;
+ }
+ unix_midi_output_device *outdev = find_midi_output(currprefs.win32_midioutdev);
+ unix_midi_output_device *indev = currprefs.win32_midiindev >= 0 ? find_midi_input(currprefs.win32_midiindev) : NULL;
+ if (!outdev) {
+ write_log(_T("MIDI OUT: no output device for id %d\n"), currprefs.win32_midioutdev);
+ return 0;
+ }
+ if (outdev->emulated) {
+ return 0;
+ }
+#if defined(WINUAE_UNIX_WITH_COREMIDI)
+ if (MIDIClientCreate(CFSTR("WinUAE Unix MIDI"), NULL, NULL, &midi_client) != noErr) {
+ write_log(_T("MIDI OUT: MIDIClientCreate failed\n"));
+ return 0;
+ }
+ if (MIDIOutputPortCreate(midi_client, CFSTR("WinUAE Unix MIDI Out"), &midi_out_port) != noErr) {
+ MIDIClientDispose(midi_client);
+ midi_client = 0;
+ write_log(_T("MIDI OUT: MIDIOutputPortCreate failed\n"));
+ return 0;
+ }
+ midi_out_endpoint = outdev->endpoint;
+ if (indev) {
+ if (MIDIInputPortCreate(midi_client, CFSTR("WinUAE Unix MIDI In"), coremidi_input_proc, NULL, &midi_in_port) == noErr
+ && MIDIPortConnectSource(midi_in_port, indev->endpoint, NULL) == noErr) {
+ midi_in_endpoint = indev->endpoint;
+ midi_in_ready = true;
+ } else {
+ if (midi_in_port) {
+ MIDIPortDispose(midi_in_port);
+ midi_in_port = 0;
+ }
+ write_log(_T("MIDI IN: failed to open %s\n"), indev->name);
+ }
+ }
+#elif defined(WINUAE_UNIX_WITH_ALSA_MIDI)
+ if (snd_seq_open(&alsa_seq, "default", indev ? SND_SEQ_OPEN_DUPLEX : SND_SEQ_OPEN_OUTPUT, 0) < 0) {
+ write_log(_T("MIDI OUT: failed to open ALSA sequencer\n"));
+ return 0;
+ }
+ snd_seq_set_client_name(alsa_seq, "WinUAE Unix MIDI");
+ alsa_out_port = snd_seq_create_simple_port(alsa_seq, "WinUAE MIDI Out",
+ SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ,
+ SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION);
+ if (alsa_out_port < 0 || snd_seq_connect_to(alsa_seq, alsa_out_port, outdev->client, outdev->port) < 0) {
+ snd_seq_close(alsa_seq);
+ alsa_seq = NULL;
+ alsa_out_port = -1;
+ write_log(_T("MIDI OUT: failed to connect ALSA port %s\n"), outdev->name);
+ return 0;
+ }
+ if (snd_midi_event_new(BUFFLEN, &alsa_encoder) < 0) {
+ snd_seq_close(alsa_seq);
+ alsa_seq = NULL;
+ alsa_out_port = -1;
+ write_log(_T("MIDI OUT: failed to create ALSA MIDI encoder\n"));
+ return 0;
+ }
+ snd_midi_event_init(alsa_encoder);
+ if (indev) {
+ alsa_in_port = snd_seq_create_simple_port(alsa_seq, "WinUAE MIDI In",
+ SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE,
+ SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION);
+ if (alsa_in_port >= 0
+ && snd_seq_connect_from(alsa_seq, alsa_in_port, indev->client, indev->port) >= 0
+ && snd_midi_event_new(BUFFLEN, &alsa_decoder) >= 0) {
+ snd_midi_event_init(alsa_decoder);
+ snd_seq_nonblock(alsa_seq, 1);
+ midi_in_ready = true;
+ } else {
+ if (alsa_decoder) {
+ snd_midi_event_free(alsa_decoder);
+ alsa_decoder = NULL;
+ }
+ write_log(_T("MIDI IN: failed to connect ALSA port %s\n"), indev->name);
+ }
+ }
+#endif
+ midi_reset_parser();
+ midi_clear_input_queue();
+ midi_ready = TRUE;
+ serdev = 1;
+ write_log(_T("MIDI OUT: using %s\n"), outdev->name);
+ if (midi_in_ready && indev) {
+ write_log(_T("MIDI IN: using %s\n"), indev->name);
+ }
+ return 1;
+}
+
+void Midi_Close(void)
+{
+ if (!midi_ready) {
+ return;
+ }
+#if defined(WINUAE_UNIX_WITH_COREMIDI)
+ if (midi_in_port) {
+ if (midi_in_endpoint) {
+ MIDIPortDisconnectSource(midi_in_port, midi_in_endpoint);
+ }
+ MIDIPortDispose(midi_in_port);
+ midi_in_port = 0;
+ }
+ midi_in_endpoint = 0;
+ if (midi_out_port) {
+ MIDIPortDispose(midi_out_port);
+ midi_out_port = 0;
+ }
+ if (midi_client) {
+ MIDIClientDispose(midi_client);
+ midi_client = 0;
+ }
+ midi_out_endpoint = 0;
+#elif defined(WINUAE_UNIX_WITH_ALSA_MIDI)
+ if (alsa_decoder) {
+ snd_midi_event_free(alsa_decoder);
+ alsa_decoder = NULL;
+ }
+ if (alsa_encoder) {
+ snd_midi_event_free(alsa_encoder);
+ alsa_encoder = NULL;
+ }
+ if (alsa_seq) {
+ snd_seq_close(alsa_seq);
+ alsa_seq = NULL;
+ }
+ alsa_out_port = -1;
+ alsa_in_port = -1;
+#endif
+ midi_in_ready = false;
+ midi_ready = FALSE;
+ midi_reset_parser();
+ midi_clear_input_queue();
+ write_log(_T("MIDI: closed.\n"));
+}
+
+void Midi_Reopen(void)
+{
+ if (midi_ready) {
+ Midi_Close();
+ Midi_Open();
+ }
+}
+
+int ismidibyte(void)
+{
+ poll_alsa_midi_input();
+ std::lock_guard<std::mutex> lock(input_queue_mutex);
+ return input_queue.empty() ? 0 : 1;
+}
+
+LONG getmidibyte(void)
+{
+ poll_alsa_midi_input();
+ std::lock_guard<std::mutex> lock(input_queue_mutex);
+ if (input_queue.empty()) {
+ return -1;
+ }
+ LONG value = input_queue.front();
+ input_queue.pop_front();
+ return value;
+}
+
+#endif
--- /dev/null
+/*
+ * Unix native MIDI backend.
+ *
+ * WinUAE routes MIDI through serial emulation when the guest selects the MIDI
+ * baud rate. This header mirrors the small Win32 MIDI API used by that path.
+ */
+#ifndef UAE_UNIX_MIDI_H
+#define UAE_UNIX_MIDI_H
+
+#define MIDI_BUFFERS 2
+#define BUFFLEN 32766
+
+#define MIDI_NOTEOFF 0x80
+#define MIDI_NOTEON 0x90
+#define MIDI_PTOUCH 0xA0
+#define MIDI_CCHANGE 0xB0
+#define MIDI_PCHANGE 0xC0
+#define MIDI_MTOUCH 0xD0
+#define MIDI_PBEND 0xE0
+#define MIDI_SYSX 0xF0
+#define MIDI_MTC 0xF1
+#define MIDI_SONGPP 0xF2
+#define MIDI_SONGS 0xF3
+#define MIDI_EOX 0xF7
+#define MIDI_CLOCK 0xF8
+#define MIDI_START 0xFA
+#define MIDI_CONTINUE 0xFB
+#define MIDI_STOP 0xFC
+#define MIDI_SENSE 0xFE
+
+#ifndef _WIN32
+typedef int LONG;
+#endif
+
+typedef struct mos
+{
+ BYTE status;
+ BYTE byte1;
+ BYTE byte2;
+ int length;
+ int posn;
+ int sysex;
+ int timecode;
+ int unknown;
+} MidiOutStatus;
+
+typedef enum
+{
+ midi_input,
+ midi_output
+} midi_direction_e;
+
+extern BOOL midi_ready;
+
+extern int Midi_Parse(midi_direction_e direction, BYTE *c);
+extern int Midi_Open(void);
+extern void Midi_Close(void);
+extern void Midi_Reopen(void);
+extern LONG getmidibyte(void);
+extern int ismidibyte(void);
+
+extern int unix_midi_output_device_count(void);
+extern int unix_midi_output_device_id(int index);
+extern const TCHAR *unix_midi_output_device_display_name(int index);
+extern const TCHAR *unix_midi_output_device_config_name_for_id(int devid);
+extern int unix_midi_output_device_id_from_config_name(const TCHAR *name);
+extern int unix_midi_input_device_count(void);
+extern int unix_midi_input_device_id(int index);
+extern const TCHAR *unix_midi_input_device_display_name(int index);
+extern const TCHAR *unix_midi_input_device_config_name_for_id(int devid);
+extern int unix_midi_input_device_id_from_config_name(const TCHAR *name);
+
+#endif
--- /dev/null
+#include "sysconfig.h"
+#include "sysdeps.h"
+
+#include "options.h"
+#include "parallel.h"
+#include "cia.h"
+
+#if defined(__linux__)
+#include <fcntl.h>
+#include <linux/parport.h>
+#include <linux/ppdev.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#endif
+
+static FILE *printer_output;
+static bool printer_is_pipe;
+
+#if defined(__linux__)
+static int parallel_fd = -1;
+static TCHAR parallel_fd_name[MAX_DPATH];
+static uae_u8 parallel_control;
+#endif
+
+static bool starts_with(const char *s, const char *prefix)
+{
+ return strncmp(s, prefix, strlen(prefix)) == 0;
+}
+
+static bool openprinter(void)
+{
+ if (printer_output) {
+ return true;
+ }
+ if (!currprefs.prtname[0] || !_tcsicmp(currprefs.prtname, _T("none"))) {
+ return false;
+ }
+
+ const char *spec = currprefs.prtname;
+ if (!_tcsicmp(currprefs.prtname, _T("default"))) {
+ spec = DEFPRTNAME;
+ }
+
+ if (spec[0] == '|') {
+ const char *command = spec + 1;
+ while (*command && isspace((unsigned char)*command)) {
+ command++;
+ }
+ if (!*command) {
+ write_log(_T("PARALLEL: empty printer command '%s'\n"), currprefs.prtname);
+ return false;
+ }
+ printer_output = popen(command, "w");
+ printer_is_pipe = true;
+ } else if (!strcmp(spec, "lpr") || starts_with(spec, "lpr ") || !strcmp(spec, "lp") || starts_with(spec, "lp ")) {
+ printer_output = popen(spec, "w");
+ printer_is_pipe = true;
+ } else {
+ TCHAR path[MAX_DPATH];
+ target_expand_environment(spec, path, MAX_DPATH);
+ printer_output = fopen(path, "ab");
+ printer_is_pipe = false;
+ }
+
+ if (!printer_output) {
+ write_log(_T("PARALLEL: failed to open printer target '%s': %s\n"), currprefs.prtname, strerror(errno));
+ return false;
+ }
+ write_log(_T("PARALLEL: printer output opened: %s\n"), currprefs.prtname);
+ return true;
+}
+
+#if defined(__linux__)
+static bool direct_parallel_spec(const TCHAR *name)
+{
+ return name && (starts_with(name, "/dev/parport") || starts_with(name, "parport:"));
+}
+
+static const TCHAR *direct_parallel_path(const TCHAR *name)
+{
+ if (name && starts_with(name, "parport:")) {
+ return name + 8;
+ }
+ return name;
+}
+
+static void close_parallel_direct(void)
+{
+ if (parallel_fd >= 0) {
+ ioctl(parallel_fd, PPRELEASE);
+ close(parallel_fd);
+ parallel_fd = -1;
+ }
+ parallel_fd_name[0] = 0;
+ parallel_control = 0;
+}
+
+static bool open_parallel_direct(void)
+{
+ if (!direct_parallel_spec(currprefs.prtname)) {
+ close_parallel_direct();
+ return false;
+ }
+ const TCHAR *path = direct_parallel_path(currprefs.prtname);
+ if (parallel_fd >= 0 && !_tcscmp(parallel_fd_name, path)) {
+ return true;
+ }
+
+ close_parallel_direct();
+ parallel_fd = open(path, O_RDWR | O_CLOEXEC);
+ if (parallel_fd < 0) {
+ write_log(_T("PARALLEL: failed to open direct port '%s': %s\n"), path, strerror(errno));
+ return false;
+ }
+ if (ioctl(parallel_fd, PPCLAIM) < 0) {
+ write_log(_T("PARALLEL: failed to claim direct port '%s': %s\n"), path, strerror(errno));
+ close_parallel_direct();
+ return false;
+ }
+
+ int direction = 0;
+ ioctl(parallel_fd, PPDATADIR, &direction);
+ parallel_control = PARPORT_CONTROL_INIT | PARPORT_CONTROL_SELECT;
+ ioctl(parallel_fd, PPWCONTROL, ¶llel_control);
+ _tcsncpy(parallel_fd_name, path, sizeof parallel_fd_name / sizeof(TCHAR) - 1);
+ parallel_fd_name[sizeof parallel_fd_name / sizeof(TCHAR) - 1] = 0;
+ write_log(_T("PARALLEL: direct port opened: %s\n"), path);
+ return true;
+}
+#else
+static bool direct_parallel_spec(const TCHAR *)
+{
+ return false;
+}
+
+static void close_parallel_direct(void)
+{
+}
+
+static bool open_parallel_direct(void)
+{
+ return false;
+}
+#endif
+
+int isprinter(void)
+{
+ if (!currprefs.prtname[0] || !_tcsicmp(currprefs.prtname, _T("none"))) {
+ return 0;
+ }
+ if (direct_parallel_spec(currprefs.prtname)) {
+ return open_parallel_direct() ? -1 : 0;
+ }
+ return 1;
+}
+
+void doprinter(uae_u8 val)
+{
+ if (!openprinter()) {
+ return;
+ }
+ if (fputc(val, printer_output) == EOF) {
+ write_log(_T("PARALLEL: printer write failed: %s\n"), strerror(errno));
+ closeprinter();
+ }
+}
+
+void flushprinter(void)
+{
+ if (printer_output) {
+ fflush(printer_output);
+ }
+}
+
+void closeprinter(void)
+{
+ if (!printer_output) {
+ return;
+ }
+ flushprinter();
+ if (printer_is_pipe) {
+ pclose(printer_output);
+ } else {
+ fclose(printer_output);
+ }
+ printer_output = NULL;
+ printer_is_pipe = false;
+}
+
+int isprinteropen(void)
+{
+ return printer_output != NULL;
+}
+
+void initparallel(void)
+{
+ closeprinter();
+ close_parallel_direct();
+}
+
+int parallel_direct_write_data(uae_u8 v, uae_u8 dir)
+{
+#if defined(__linux__)
+ if (!open_parallel_direct() || dir != 0xff) {
+ return 0;
+ }
+ int direction = 0;
+ ioctl(parallel_fd, PPDATADIR, &direction);
+ if (ioctl(parallel_fd, PPWDATA, &v) < 0) {
+ write_log(_T("PARALLEL: direct data write failed: %s\n"), strerror(errno));
+ return 0;
+ }
+ uae_u8 control = parallel_control & ~PARPORT_CONTROL_STROBE;
+ ioctl(parallel_fd, PPWCONTROL, &control);
+ control |= PARPORT_CONTROL_STROBE;
+ ioctl(parallel_fd, PPWCONTROL, &control);
+ control &= ~PARPORT_CONTROL_STROBE;
+ ioctl(parallel_fd, PPWCONTROL, &control);
+ parallel_control = control;
+ return 1;
+#else
+ return 0;
+#endif
+}
+
+int parallel_direct_read_data(uae_u8 *v)
+{
+#if defined(__linux__)
+ if (!open_parallel_direct() || !v) {
+ return 0;
+ }
+ int direction = 1;
+ ioctl(parallel_fd, PPDATADIR, &direction);
+ if (ioctl(parallel_fd, PPRDATA, v) < 0) {
+ write_log(_T("PARALLEL: direct data read failed: %s\n"), strerror(errno));
+ return 0;
+ }
+ return 1;
+#else
+ return 0;
+#endif
+}
+
+int parallel_direct_write_status(uae_u8 v, uae_u8 dir)
+{
+#if defined(__linux__)
+ if (!open_parallel_direct()) {
+ return 0;
+ }
+ if (dir & 3) {
+ write_log(_T("PARALLEL: BUSY and POUT can't be driven by direct output\n"));
+ }
+ if (dir & 4) {
+ if (v & 4) {
+ parallel_control &= ~PARPORT_CONTROL_SELECT;
+ } else {
+ parallel_control |= PARPORT_CONTROL_SELECT;
+ }
+ if (ioctl(parallel_fd, PPWCONTROL, ¶llel_control) < 0) {
+ write_log(_T("PARALLEL: direct control write failed: %s\n"), strerror(errno));
+ return 0;
+ }
+ }
+ return (dir & 3) ? 0 : 1;
+#else
+ return 0;
+#endif
+}
+
+int parallel_direct_read_status(uae_u8 *v)
+{
+#if defined(__linux__)
+ if (!open_parallel_direct() || !v) {
+ return 0;
+ }
+ uae_u8 status = 0;
+ if (ioctl(parallel_fd, PPRSTATUS, &status) < 0) {
+ write_log(_T("PARALLEL: direct status read failed: %s\n"), strerror(errno));
+ return 0;
+ }
+ uae_u8 out = 0;
+ if (status & PARPORT_STATUS_SELECT) {
+ out |= 4;
+ }
+ if (status & PARPORT_STATUS_PAPEROUT) {
+ out |= 2;
+ }
+ if (!(status & PARPORT_STATUS_BUSY)) {
+ out |= 1;
+ }
+ *v &= ~7;
+ *v |= out & 7;
+ if (status & PARPORT_STATUS_ACK) {
+ cia_parallelack();
+ }
+ return 1;
+#else
+ return 0;
+#endif
+}
--- /dev/null
+#include "sysconfig.h"
+#include "sysdeps.h"
+
+#include "gfxboard.h"
+#include "pcem/device.h"
+#include "pcem/io.h"
+#include "pcem/timer.h"
+#include "threaddep/thread.h"
+
+#include <unistd.h>
+
+typedef struct mem_mapping_t mem_mapping_t;
+
+void mem_mapping_addx(mem_mapping_t *mapping, uint32_t base, uint32_t size,
+ uint8_t (*read_b)(uint32_t addr, void *p),
+ uint16_t (*read_w)(uint32_t addr, void *p),
+ uint32_t (*read_l)(uint32_t addr, void *p),
+ void (*write_b)(uint32_t addr, uint8_t val, void *p),
+ void (*write_w)(uint32_t addr, uint16_t val, void *p),
+ void (*write_l)(uint32_t addr, uint32_t val, void *p),
+ uint8_t *exec, uint32_t flags, void *p);
+void mem_mapping_set_handlerx(mem_mapping_t *mapping,
+ uint8_t (*read_b)(uint32_t addr, void *p),
+ uint16_t (*read_w)(uint32_t addr, void *p),
+ uint32_t (*read_l)(uint32_t addr, void *p),
+ void (*write_b)(uint32_t addr, uint8_t val, void *p),
+ void (*write_w)(uint32_t addr, uint16_t val, void *p),
+ void (*write_l)(uint32_t addr, uint32_t val, void *p));
+void mem_mapping_set_px(mem_mapping_t *mapping, void *p);
+void mem_mapping_set_addrx(mem_mapping_t *mapping, uint32_t base, uint32_t size);
+void mem_mapping_disablex(mem_mapping_t *mapping);
+void mem_mapping_enablex(mem_mapping_t *mapping);
+
+#ifndef WITH_X86
+uint64_t tsc;
+uint64_t VGACONST1;
+uint64_t VGACONST2;
+float cpuclock;
+int has_vlb;
+
+static struct pcem_timer_defaults
+{
+ pcem_timer_defaults()
+ {
+ if (!cpuclock) {
+ cpuclock = 33000000.0f;
+ }
+ if (!VGACONST1) {
+ VGACONST1 = (uint64_t)((cpuclock / 25175000.0) * (float)(1ULL << 32));
+ }
+ if (!VGACONST2) {
+ VGACONST2 = (uint64_t)((cpuclock / 28322000.0) * (float)(1ULL << 32));
+ }
+ if (!TIMER_USEC) {
+ TIMER_USEC = 1ULL << 32;
+ }
+ }
+} pcem_timer_defaults_instance;
+#endif
+
+HANDLE CreateSemaphore(void*, int, int initial_count, const char*)
+{
+ uae_sem_t sem = NULL;
+ uae_sem_init(&sem, 0, initial_count > 0 ? 1 : 0);
+ return (HANDLE)sem;
+}
+
+DWORD WaitForSingleObject(HANDLE handle, DWORD timeout)
+{
+ uae_sem_t sem = (uae_sem_t)handle;
+ int ms = timeout == INFINITE ? -1 : (int)timeout;
+ return uae_sem_trywait_delay(&sem, ms) == 0 ? WAIT_OBJECT_0 : WAIT_TIMEOUT;
+}
+
+BOOL ReleaseSemaphore(HANDLE handle, int, void*)
+{
+ uae_sem_t sem = (uae_sem_t)handle;
+ uae_sem_post(&sem);
+ return TRUE;
+}
+
+BOOL CloseHandle(HANDLE handle)
+{
+ uae_sem_t sem = (uae_sem_t)handle;
+ uae_sem_destroy(&sem);
+ return TRUE;
+}
+
+#ifndef WITH_X86
+void mem_mapping_add(mem_mapping_t *mapping,
+ uint32_t base,
+ uint32_t size,
+ uint8_t (*read_b)(uint32_t addr, void *p),
+ uint16_t (*read_w)(uint32_t addr, void *p),
+ uint32_t (*read_l)(uint32_t addr, void *p),
+ void (*write_b)(uint32_t addr, uint8_t val, void *p),
+ void (*write_w)(uint32_t addr, uint16_t val, void *p),
+ void (*write_l)(uint32_t addr, uint32_t val, void *p),
+ uint8_t *exec,
+ uint32_t flags,
+ void *p)
+{
+ mem_mapping_addx(mapping, base, size, read_b, read_w, read_l, write_b,
+ write_w, write_l, exec, flags, p);
+}
+
+void mem_mapping_set_handler(mem_mapping_t *mapping,
+ uint8_t (*read_b)(uint32_t addr, void *p),
+ uint16_t (*read_w)(uint32_t addr, void *p),
+ uint32_t (*read_l)(uint32_t addr, void *p),
+ void (*write_b)(uint32_t addr, uint8_t val, void *p),
+ void (*write_w)(uint32_t addr, uint16_t val, void *p),
+ void (*write_l)(uint32_t addr, uint32_t val, void *p))
+{
+ mem_mapping_set_handlerx(mapping, read_b, read_w, read_l, write_b,
+ write_w, write_l);
+}
+
+void mem_mapping_set_p(mem_mapping_t *mapping, void *p)
+{
+ mem_mapping_set_px(mapping, p);
+}
+
+void mem_mapping_set_addr(mem_mapping_t *mapping, uint32_t base, uint32_t size)
+{
+ mem_mapping_set_addrx(mapping, base, size);
+}
+
+void mem_mapping_set_exec(mem_mapping_t*, uint8_t*)
+{
+}
+
+void mem_mapping_disable(mem_mapping_t *mapping)
+{
+ mem_mapping_disablex(mapping);
+}
+
+void mem_mapping_enable(mem_mapping_t *mapping)
+{
+ mem_mapping_enablex(mapping);
+}
+
+void io_sethandler(uint16_t base, int size,
+ uint8_t (*inb)(uint16_t addr, void *priv),
+ uint16_t (*inw)(uint16_t addr, void *priv),
+ uint32_t (*inl)(uint16_t addr, void *priv),
+ void (*outb)(uint16_t addr, uint8_t val, void *priv),
+ void (*outw)(uint16_t addr, uint16_t val, void *priv),
+ void (*outl)(uint16_t addr, uint32_t val, void *priv),
+ void *priv)
+{
+ io_sethandlerx(base, size, inb, inw, inl, outb, outw, outl, priv);
+}
+
+void io_removehandler(uint16_t base, int size,
+ uint8_t (*inb)(uint16_t addr, void *priv),
+ uint16_t (*inw)(uint16_t addr, void *priv),
+ uint32_t (*inl)(uint16_t addr, void *priv),
+ void (*outb)(uint16_t addr, uint8_t val, void *priv),
+ void (*outw)(uint16_t addr, uint16_t val, void *priv),
+ void (*outl)(uint16_t addr, uint32_t val, void *priv),
+ void *priv)
+{
+ io_removehandlerx(base, size, inb, inw, inl, outb, outw, outl, priv);
+}
+
+extern void put_io_pcem(uaecptr addr, uae_u32 v, int size);
+extern uae_u32 get_io_pcem(uaecptr addr, int size);
+
+uint8_t portin(uint16_t portnum)
+{
+ return (uint8_t)get_io_pcem(portnum, 0);
+}
+
+void portout(uint16_t portnum, uint8_t value)
+{
+ put_io_pcem(portnum, value, 0);
+}
+
+uint16_t portin16(uint16_t portnum)
+{
+ return (uint16_t)get_io_pcem(portnum, 1);
+}
+
+void portout16(uint16_t portnum, uint16_t value)
+{
+ put_io_pcem(portnum, value, 1);
+}
+
+uint32_t portin32(uint16_t portnum)
+{
+ return get_io_pcem(portnum, 2);
+}
+
+void portout32(uint16_t portnum, uint32_t value)
+{
+ put_io_pcem(portnum, value, 2);
+}
+
+void x86_map_lfb(int)
+{
+}
+
+static int pcem_unix_render_threads(void)
+{
+ long cpus = sysconf(_SC_NPROCESSORS_ONLN);
+ if (cpus >= 8) {
+ return 4;
+ }
+ if (cpus >= 4) {
+ return 2;
+ }
+ return 1;
+}
+
+int device_get_config_int(const char *name)
+{
+ if (!strcmp(name, "bilinear") || !strcmp(name, "dithering") ||
+ !strcmp(name, "dithersub") || !strcmp(name, "dacfilter")) {
+ return 1;
+ }
+ if (!strcmp(name, "recompiler")) {
+#ifdef PCEM_VOODOO_CODEGEN
+ return 1;
+#else
+ return 0;
+#endif
+ }
+ if (!strcmp(name, "sli") || !strcmp(name, "type")) {
+ return 0;
+ }
+ if (!strcmp(name, "framebuffer_memory") ||
+ !strcmp(name, "texture_memory")) {
+ return 2;
+ }
+ if (!strcmp(name, "memory")) {
+ int vram = pcem_getvramsize() >> 20;
+ return vram > 0 ? vram : 2;
+ }
+ if (!strcmp(name, "render_threads")) {
+ return pcem_unix_render_threads();
+ }
+ return 0;
+}
+#endif
+
+char *device_get_config_string(const char*)
+{
+ return NULL;
+}
+
+char *current_device_name;
--- /dev/null
+#ifndef WINUAE_OD_UNIX_PCEM_WINCOMPAT_H
+#define WINUAE_OD_UNIX_PCEM_WINCOMPAT_H
+
+#include <stdint.h>
+#include <stddef.h>
+
+#ifdef __cplusplus
+#include <cstdlib>
+#endif
+
+#ifndef UAE
+#define UAE
+#endif
+
+#ifndef __cdecl
+#define __cdecl
+#endif
+
+#ifndef container_of
+#define container_of(address, type, field) \
+ ((type *)((char *)(address) - offsetof(type, field)))
+#endif
+
+#if !defined(_MSC_VER)
+static inline uint16_t _byteswap_ushort(uint16_t v)
+{
+ return __builtin_bswap16(v);
+}
+
+static inline uint32_t _byteswap_ulong(uint32_t v)
+{
+ return __builtin_bswap32(v);
+}
+#endif
+
+#endif
--- /dev/null
+#include "sysconfig.h"
+#include "sysdeps.h"
+
+#include <sys/stat.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include "options.h"
+#include "arcadia.h"
+#include "fsdb.h"
+#include "rommgr.h"
+#include "uae/string.h"
+#include "uae.h"
+#include "zfile.h"
+
+struct detected_rom {
+ TCHAR path[MAX_DPATH];
+ struct romdata *rd;
+ int priority;
+};
+
+struct romscan_state {
+ std::vector<detected_rom> detected;
+ bool got;
+};
+
+static bool romscan_dirty = true;
+static bool romscan_recursive;
+
+static bool is_rom_extension(const TCHAR *path, bool deepscan)
+{
+ const TCHAR *ext;
+
+ if (!path) {
+ return false;
+ }
+ ext = _tcsrchr(path, '.');
+ if (!ext) {
+ return false;
+ }
+ ext++;
+
+ if (!_tcsicmp(ext, _T("rom")) || !_tcsicmp(ext, _T("bin")) || !_tcsicmp(ext, _T("adf")) || !_tcsicmp(ext, _T("key"))
+ || !_tcsicmp(ext, _T("a500")) || !_tcsicmp(ext, _T("a600"))
+ || !_tcsicmp(ext, _T("a1200")) || !_tcsicmp(ext, _T("a3000")) || !_tcsicmp(ext, _T("a4000")) || !_tcsicmp(ext, _T("cd32"))) {
+ return true;
+ }
+ if (_tcslen(ext) >= 2 && toupper(ext[0]) == 'U' && isdigit(ext[1])) {
+ return true;
+ }
+ if (!deepscan) {
+ return false;
+ }
+ for (int i = 0; uae_archive_extensions[i]; i++) {
+ if (!_tcsicmp(ext, uae_archive_extensions[i])) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static int rom_priority(const TCHAR *path, int size)
+{
+ const TCHAR *ext = _tcsrchr(path, '.');
+ if (!ext) {
+ return 80;
+ }
+
+ struct stat st;
+ int pri = 10;
+ if (stat(path, &st) == 0) {
+ if (st.st_size == size) {
+ pri--;
+ }
+ } else {
+ pri = 100;
+ }
+ return pri;
+}
+
+static bool same_rom_identity(const struct romdata *a, const struct romdata *b)
+{
+ return a && b && a->id == b->id && a->group == b->group;
+}
+
+static void add_detected_rom(romscan_state *state, const TCHAR *path, struct romdata *rd)
+{
+ if (!state || !path || !rd) {
+ return;
+ }
+
+ detected_rom rom;
+ uae_tcslcpy(rom.path, path, sizeof rom.path / sizeof(TCHAR));
+ if (rom.path[0] != ':') {
+ fullpath(rom.path, sizeof rom.path / sizeof(TCHAR));
+ }
+ rom.rd = rd;
+ rom.priority = rom_priority(rom.path, rd->size);
+
+ for (detected_rom &existing : state->detected) {
+ if (same_rom_identity(existing.rd, rd)) {
+ if (rom.priority < existing.priority) {
+ existing = rom;
+ }
+ return;
+ }
+ }
+ state->detected.push_back(rom);
+}
+
+static int scan_zfile_rom(struct zfile *file, void *userdata)
+{
+ romscan_state *state = static_cast<romscan_state*>(userdata);
+ const TCHAR *path = zfile_getname(file);
+ const TCHAR *romkey = _T("rom.key");
+
+ if (!is_rom_extension(path, true)) {
+ return 0;
+ }
+
+ struct romdata *rd = scan_single_rom_file(file);
+ if (rd) {
+ add_detected_rom(state, path, rd);
+ if (rd->type & ROMTYPE_KEY) {
+ addkeyfile(path);
+ }
+ state->got = true;
+ } else if (_tcslen(path) > _tcslen(romkey) && !_tcsicmp(path + _tcslen(path) - _tcslen(romkey), romkey)) {
+ addkeyfile(path);
+ }
+ return 0;
+}
+
+static bool scan_rom_file(const TCHAR *path, romscan_state *state)
+{
+ if (!is_rom_extension(path, true)) {
+ return false;
+ }
+
+ const bool had_rom = state->got;
+ for (int cnt = 0;; cnt++) {
+ TCHAR tmp[MAX_DPATH];
+ uae_tcslcpy(tmp, path, sizeof tmp / sizeof(TCHAR));
+ struct romdata *rd = scan_arcadia_rom(tmp, cnt);
+ if (!rd) {
+ break;
+ }
+ add_detected_rom(state, tmp, rd);
+ state->got = true;
+ }
+ zfile_zopen(path, scan_zfile_rom, state);
+ return state->got != had_rom;
+}
+
+static void scan_rom_directory(const TCHAR *path, bool recursive, int level, romscan_state *state)
+{
+ struct my_opendir_s *dir = my_opendir(path);
+ if (!dir) {
+ return;
+ }
+
+ TCHAR name[MAX_DPATH];
+ while (my_readdir(dir, name)) {
+ if (!_tcscmp(name, _T(".")) || !_tcscmp(name, _T(".."))) {
+ continue;
+ }
+
+ TCHAR full[MAX_DPATH];
+ uae_tcslcpy(full, path, sizeof full / sizeof(TCHAR));
+ fixtrailing(full);
+ if (_tcslen(full) + _tcslen(name) >= sizeof full / sizeof(TCHAR)) {
+ continue;
+ }
+ _tcscat(full, name);
+
+ struct stat st;
+ if (stat(full, &st) != 0) {
+ continue;
+ }
+ if (S_ISREG(st.st_mode) && st.st_size < 10000000) {
+ scan_rom_file(full, state);
+ } else if (recursive && S_ISDIR(st.st_mode) && level < 2 && name[0] != '.') {
+ scan_rom_directory(full, recursive, level + 1, state);
+ }
+ }
+ my_closedir(dir);
+}
+
+static bool add_scan_path(std::vector<std::string> *paths, const TCHAR *path)
+{
+ if (!path || !path[0]) {
+ return false;
+ }
+
+ TCHAR full[MAX_DPATH];
+ uae_tcslcpy(full, path, sizeof full / sizeof(TCHAR));
+ fullpath(full, sizeof full / sizeof(TCHAR));
+ fixtrailing(full);
+ if (!full[0]) {
+ return false;
+ }
+
+ std::string value(full);
+ if (std::find(paths->begin(), paths->end(), value) != paths->end()) {
+ return false;
+ }
+ paths->push_back(value);
+ return true;
+}
+
+static std::vector<std::string> rom_scan_paths(struct uae_prefs *prefs)
+{
+ std::vector<std::string> paths;
+
+ if (prefs) {
+ for (int i = 0; i < MAX_PATHS; i++) {
+ add_scan_path(&paths, prefs->path_rom.path[i]);
+ }
+ }
+
+ TCHAR path[MAX_DPATH];
+ fetch_rompath(path, sizeof path / sizeof(TCHAR));
+ add_scan_path(&paths, path);
+
+ return paths;
+}
+
+static void add_nofile_roms(romscan_state *state)
+{
+ for (int id = 1;; id++) {
+ struct romdata *rd = getromdatabyid(id);
+ if (!rd) {
+ break;
+ }
+ if (rd->crc32 != 0xffffffff) {
+ continue;
+ }
+
+ TCHAR path[MAX_DPATH];
+ if (rd->configname) {
+ _stprintf(path, _T(":%s"), rd->configname);
+ } else {
+ _stprintf(path, _T(":ROM_%03d"), rd->id);
+ }
+ add_detected_rom(state, path, rd);
+ }
+}
+
+static int scan_rom_paths(struct uae_prefs *prefs)
+{
+ romscan_state state;
+ state.got = false;
+
+ for (const std::string &path : rom_scan_paths(prefs)) {
+ write_log(_T("ROM scan directory '%s'\n"), path.c_str());
+ scan_rom_directory(path.c_str(), romscan_recursive, 0, &state);
+ }
+ add_nofile_roms(&state);
+
+ for (const detected_rom &rom : state.detected) {
+ romlist_add(rom.path, rom.rd);
+ }
+ return (int)state.detected.size();
+}
+
+void unix_romscan_mark_dirty(void)
+{
+ romscan_dirty = true;
+}
+
+void unix_romscan_set_recursive(bool recursive)
+{
+ if (romscan_recursive != recursive) {
+ romscan_recursive = recursive;
+ unix_romscan_mark_dirty();
+ }
+}
+
+void unix_romscan_refresh(struct uae_prefs *prefs, bool force)
+{
+ if (!force && !romscan_dirty) {
+ return;
+ }
+
+ romlist_clear();
+ load_keyring(prefs, NULL);
+ const int keys = get_keyring();
+ int count = scan_rom_paths(prefs);
+ if (get_keyring() > keys) {
+ romlist_clear();
+ load_keyring(prefs, NULL);
+ count = scan_rom_paths(prefs);
+ }
+ romlist_add(NULL, NULL);
+ write_log(_T("ROM scan found %d known ROM%s\n"), count, count == 1 ? _T("") : _T("s"));
+ romscan_dirty = false;
+}
--- /dev/null
+#pragma once
+
+struct uae_prefs;
+
+void unix_romscan_mark_dirty(void);
+void unix_romscan_set_recursive(bool recursive);
+void unix_romscan_refresh(struct uae_prefs *prefs, bool force);
--- /dev/null
+#include "sysconfig.h"
+#include "sysdeps.h"
+
+#ifdef SERIAL_PORT
+
+#include <netdb.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <sys/ioctl.h>
+#include <termios.h>
+
+#include "custom.h"
+#include "options.h"
+#include "serial.h"
+#include "uae.h"
+#ifdef WITH_MIDI
+#include "midi.h"
+#endif
+#ifdef WITH_MIDIEMU
+#include "midiemu.h"
+#endif
+
+#define SERIALDEBUG 0
+#define SERIAL_LOOPBACK _T("LOOPBACK_SERIAL")
+
+void serial_open(void);
+void serial_close(void);
+
+int serdev;
+int seriallog;
+int log_sercon;
+int doreadser;
+int serstat = -1;
+uae_u16 serper;
+uae_u16 serdat;
+
+static int serial_fd = -1;
+static int listen_fd = -1;
+static int conn_fd = -1;
+static bool tcpserial;
+static bool serloop_enabled;
+static bool serempty_enabled;
+static bool rx_full;
+static bool rx_irq;
+static bool ovrun;
+static bool dtr;
+static bool telnet_iac;
+static int telnet_skip;
+static uae_u16 serdatr;
+static uae_u8 oldserbits;
+static uae_u8 serial_send_previous = 0xff;
+static struct termios saved_tios;
+static bool saved_tios_valid;
+
+static void set_nonblock(int fd)
+{
+ int flags = fcntl(fd, F_GETFL, 0);
+ if (flags >= 0) {
+ fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+ }
+}
+
+static speed_t baud_to_speed(int baud)
+{
+ switch (baud) {
+ case 300: return B300;
+ case 1200: return B1200;
+ case 2400: return B2400;
+ case 4800: return B4800;
+ case 9600: return B9600;
+ case 19200: return B19200;
+ case 38400: return B38400;
+ case 57600: return B57600;
+ case 115200: return B115200;
+#ifdef B230400
+ case 230400: return B230400;
+#endif
+ default: return B9600;
+ }
+}
+
+static int serial_period_to_baud(uae_u16 v)
+{
+ if ((v & 0x7fff) == 0) {
+ return 0;
+ }
+ const double hz = currprefs.ntscmode ? 3579545.0 : 3546895.0;
+ const int baud = (int)(hz / (double)((v & 0x7fff) + 1) + 0.5);
+ const int standard[] = { 300, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200, 230400 };
+ int best = standard[0];
+ int bestdiff = abs(baud - best);
+ if (baud >= 30000 && baud <= 32500) {
+ return 31400;
+ }
+ for (int i = 1; i < (int)(sizeof standard / sizeof standard[0]); i++) {
+ int diff = abs(baud - standard[i]);
+ if (diff < bestdiff) {
+ best = standard[i];
+ bestdiff = diff;
+ }
+ }
+ return best;
+}
+
+static bool configure_serial_fd(int baud)
+{
+ if (serial_fd < 0) {
+ return false;
+ }
+ struct termios tios;
+ if (tcgetattr(serial_fd, &tios) < 0) {
+ write_log(_T("SERIAL: tcgetattr failed: %s\n"), strerror(errno));
+ return false;
+ }
+ if (!saved_tios_valid) {
+ saved_tios = tios;
+ saved_tios_valid = true;
+ }
+ cfmakeraw(&tios);
+ speed_t speed = baud_to_speed(baud > 0 ? baud : 9600);
+ cfsetispeed(&tios, speed);
+ cfsetospeed(&tios, speed);
+ tios.c_cflag |= CLOCAL | CREAD;
+ if (currprefs.serial_hwctsrts) {
+#ifdef CRTSCTS
+ tios.c_cflag |= CRTSCTS;
+#endif
+ } else {
+#ifdef CRTSCTS
+ tios.c_cflag &= ~CRTSCTS;
+#endif
+ }
+ tios.c_cflag &= ~CSTOPB;
+ if (currprefs.serial_stopbits) {
+ tios.c_cflag |= CSTOPB;
+ }
+ if (tcsetattr(serial_fd, TCSANOW, &tios) < 0) {
+ write_log(_T("SERIAL: tcsetattr failed: %s\n"), strerror(errno));
+ return false;
+ }
+ return true;
+}
+
+static void close_fd(int *fd)
+{
+ if (*fd >= 0) {
+ close(*fd);
+ *fd = -1;
+ }
+}
+
+static bool tcp_accept_pending(void)
+{
+ if (!tcpserial || conn_fd >= 0 || listen_fd < 0) {
+ return conn_fd >= 0;
+ }
+ struct sockaddr_storage ss;
+ socklen_t sslen = sizeof ss;
+ int fd = accept(listen_fd, (struct sockaddr *)&ss, &sslen);
+ if (fd < 0) {
+ return false;
+ }
+ set_nonblock(fd);
+ int one = 1;
+ setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof one);
+ conn_fd = fd;
+ telnet_iac = false;
+ telnet_skip = 0;
+ write_log(_T("SERIAL_TCP: connection accepted\n"));
+ return true;
+}
+
+static void tcp_disconnect(void)
+{
+ if (conn_fd >= 0) {
+ close_fd(&conn_fd);
+ write_log(_T("SERIAL_TCP: disconnect\n"));
+ }
+}
+
+static bool parse_tcp_spec(const TCHAR *spec, std::string *host, std::string *port, bool *waitmode)
+{
+ std::string s = spec ? spec : "";
+ *waitmode = false;
+ if (s.compare(0, 2, "//") == 0) {
+ s.erase(0, 2);
+ }
+ size_t slash = s.find('/');
+ if (slash != std::string::npos) {
+ std::string opt = s.substr(slash + 1);
+ s.erase(slash);
+ if (!strcasecmp(opt.c_str(), "wait")) {
+ *waitmode = true;
+ }
+ }
+ *host = "127.0.0.1";
+ *port = "1234";
+ if (s.empty()) {
+ return true;
+ }
+ size_t colon = s.rfind(':');
+ if (colon == std::string::npos) {
+ bool digits = true;
+ for (char c : s) {
+ if (!isdigit((unsigned char)c)) {
+ digits = false;
+ break;
+ }
+ }
+ if (digits) {
+ *port = s;
+ } else {
+ *host = s;
+ }
+ return true;
+ }
+ if (colon > 0) {
+ *host = s.substr(0, colon);
+ }
+ if (colon + 1 < s.size()) {
+ *port = s.substr(colon + 1);
+ }
+ return true;
+}
+
+static int opentcp(const TCHAR *sername)
+{
+ std::string host;
+ std::string port;
+ bool waitmode = false;
+ parse_tcp_spec(sername, &host, &port, &waitmode);
+
+ struct addrinfo hints;
+ memset(&hints, 0, sizeof hints);
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_PASSIVE;
+ struct addrinfo *res = NULL;
+ int err = getaddrinfo(host.c_str(), port.c_str(), &hints, &res);
+ if (err) {
+ write_log(_T("SERIAL_TCP: getaddrinfo(%s:%s) failed: %s\n"), host.c_str(), port.c_str(), gai_strerror(err));
+ return 0;
+ }
+ for (struct addrinfo *ai = res; ai; ai = ai->ai_next) {
+ listen_fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+ if (listen_fd < 0) {
+ continue;
+ }
+ int one = 1;
+ setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one);
+ if (bind(listen_fd, ai->ai_addr, ai->ai_addrlen) == 0 && listen(listen_fd, 1) == 0) {
+ break;
+ }
+ close_fd(&listen_fd);
+ }
+ freeaddrinfo(res);
+ if (listen_fd < 0) {
+ write_log(_T("SERIAL_TCP: failed to listen on %s:%s: %s\n"), host.c_str(), port.c_str(), strerror(errno));
+ return 0;
+ }
+ set_nonblock(listen_fd);
+ tcpserial = true;
+ write_log(_T("SERIAL_TCP: listening on %s:%s\n"), host.c_str(), port.c_str());
+ while (waitmode && !tcp_accept_pending()) {
+ Sleep(1000);
+ write_log(_T("SERIAL_TCP: waiting for connect...\n"));
+ }
+ return 1;
+}
+
+int openser(const TCHAR *sername)
+{
+ if (!_tcsnicmp(sername, _T("TCP://"), 6)) {
+ return opentcp(sername + 4);
+ }
+ if (!_tcsnicmp(sername, _T("TCP:"), 4)) {
+ return opentcp(sername + 4);
+ }
+ serial_fd = open(sername, O_RDWR | O_NOCTTY | O_NONBLOCK);
+ if (serial_fd < 0) {
+ write_log(_T("SERIAL: failed to open '%s': %s\n"), sername, strerror(errno));
+ return 0;
+ }
+ if (!configure_serial_fd(9600)) {
+ close_fd(&serial_fd);
+ return 0;
+ }
+ write_log(_T("SERIAL: using %s CTS/RTS=%d\n"), sername, currprefs.serial_hwctsrts);
+ return 1;
+}
+
+void closeser(void)
+{
+#ifdef WITH_MIDIEMU
+ if (midi_emu) {
+ midi_emu_close();
+ }
+#endif
+ if (serial_fd >= 0 && saved_tios_valid) {
+ tcsetattr(serial_fd, TCSANOW, &saved_tios);
+ }
+ close_fd(&serial_fd);
+ close_fd(&conn_fd);
+ close_fd(&listen_fd);
+ tcpserial = false;
+ saved_tios_valid = false;
+}
+
+static int serial_read_byte(int *out)
+{
+ if (tcpserial) {
+ if (!tcp_accept_pending()) {
+ return 0;
+ }
+ unsigned char c;
+ ssize_t got = recv(conn_fd, &c, 1, 0);
+ if (got == 1) {
+ *out = c;
+ return 1;
+ }
+ if (got == 0 || (errno != EAGAIN && errno != EWOULDBLOCK)) {
+ tcp_disconnect();
+ }
+ return 0;
+ }
+#ifdef WITH_MIDI
+ if (midi_ready) {
+ int value = (int)getmidibyte();
+ if (value < 0) {
+ return 0;
+ }
+ *out = value;
+ return 1;
+ }
+#endif
+ if (serial_fd < 0) {
+ return 0;
+ }
+ unsigned char c;
+ ssize_t got = read(serial_fd, &c, 1);
+ if (got == 1) {
+ *out = c;
+ return 1;
+ }
+ return 0;
+}
+
+int readser(int *buffer)
+{
+ for (;;) {
+ int value;
+ if (!serial_read_byte(&value)) {
+ return 0;
+ }
+ if (!tcpserial) {
+ *buffer = value;
+ return 1;
+ }
+ if (telnet_skip > 0) {
+ telnet_skip--;
+ continue;
+ }
+ if (telnet_iac) {
+ telnet_iac = false;
+ if (value == 255) {
+ *buffer = value;
+ return 1;
+ }
+ if (value == 251 || value == 252 || value == 253 || value == 254) {
+ telnet_skip = 1;
+ }
+ continue;
+ }
+ if (value == 255) {
+ telnet_iac = true;
+ continue;
+ }
+ *buffer = value;
+ return 1;
+ }
+}
+
+int readseravail(bool *breakcond)
+{
+ if (breakcond) {
+ *breakcond = false;
+ }
+ if (tcpserial) {
+ if (!tcp_accept_pending()) {
+ return 0;
+ }
+ fd_set fds;
+ FD_ZERO(&fds);
+ FD_SET(conn_fd, &fds);
+ struct timeval tv = { 0, 0 };
+ int ret = select(conn_fd + 1, &fds, NULL, NULL, &tv);
+ if (ret < 0) {
+ tcp_disconnect();
+ return 0;
+ }
+ return ret > 0 ? 1 : 0;
+ }
+#ifdef WITH_MIDI
+ if (midi_ready) {
+ return ismidibyte();
+ }
+#endif
+ if (serial_fd < 0 || !currprefs.use_serial) {
+ return 0;
+ }
+ int pending = 0;
+ if (ioctl(serial_fd, FIONREAD, &pending) == 0 && pending > 0) {
+ return pending;
+ }
+ return 0;
+}
+
+void writeser_flush(void)
+{
+#ifdef WITH_MIDI
+ if (midi_ready) {
+ return;
+ }
+#endif
+ if (serial_fd >= 0) {
+ tcdrain(serial_fd);
+ }
+}
+
+void writeser(int c)
+{
+ unsigned char b = (unsigned char)c;
+ if (tcpserial) {
+ if (tcp_accept_pending() && send(conn_fd, &b, 1, 0) != 1) {
+ tcp_disconnect();
+ }
+ return;
+ }
+#ifdef WITH_MIDIEMU
+ if (midi_emu) {
+ uae_u8 outchar = (uae_u8)c;
+ midi_emu_parse(&outchar, 1);
+ return;
+ }
+#endif
+#ifdef WITH_MIDI
+ if (midi_ready) {
+ BYTE outchar = (BYTE)c;
+ Midi_Parse(midi_output, &outchar);
+ return;
+ }
+#endif
+ if (serial_fd >= 0) {
+ write(serial_fd, &b, 1);
+ }
+}
+
+void flushser(void)
+{
+ int data;
+ while (readseravail(NULL) && readser(&data)) {
+ }
+}
+
+void getserstat(int *pstatus)
+{
+ int status = 0;
+ if (tcpserial) {
+ if (tcp_accept_pending()) {
+ status = TIOCM_CTS | TIOCM_DSR | TIOCM_CAR;
+ }
+ } else if (serial_fd >= 0) {
+ ioctl(serial_fd, TIOCMGET, &status);
+ }
+ *pstatus = status;
+}
+
+void setserstat(int mask, int onoff)
+{
+ if (serial_fd < 0) {
+ return;
+ }
+ int status = 0;
+ if (ioctl(serial_fd, TIOCMGET, &status) < 0) {
+ return;
+ }
+ if (onoff) {
+ status |= mask;
+ } else {
+ status &= ~mask;
+ }
+ ioctl(serial_fd, TIOCMSET, &status);
+}
+
+int setbaud(int baud, int org_baud)
+{
+#ifdef WITH_MIDI
+ if (org_baud == 31400 && currprefs.win32_midioutdev >= -1) {
+#ifdef WITH_MIDIEMU
+ if (currprefs.win32_midioutdev >= 0) {
+ const TCHAR *name = unix_midi_output_device_config_name_for_id(currprefs.win32_midioutdev);
+ if (!_tcsncmp(name, _T("Munt "), 5)) {
+ midi_emu_open(name);
+ return 1;
+ }
+ }
+#endif
+ if (!midi_ready && Midi_Open()) {
+ write_log(_T("Midi enabled\n"));
+ }
+ return 1;
+ }
+ if (midi_ready) {
+ Midi_Close();
+ }
+#endif
+#ifdef WITH_MIDIEMU
+ if (midi_emu) {
+ midi_emu_close();
+ }
+#endif
+ if (serial_fd < 0) {
+ return currprefs.use_serial ? 0 : 1;
+ }
+ return configure_serial_fd(baud) ? 1 : 0;
+}
+
+static void serial_rx_irq(void)
+{
+ rx_full = true;
+ rx_irq = true;
+ INTREQ_INT(11, 0);
+}
+
+static void checkreceive_serial(void)
+{
+ if (rx_full) {
+ return;
+ }
+ int recdata;
+ if (!readseravail(NULL) || !readser(&recdata)) {
+ return;
+ }
+ if (currprefs.serial_crlf) {
+ static int previous = -1;
+ if (recdata == 0 || (previous == 13 && recdata == 10)) {
+ previous = -1;
+ return;
+ }
+ previous = recdata;
+ }
+ serdatr = (uae_u16)((recdata & 0xff) | 0x0100);
+ serial_rx_irq();
+}
+
+void SERPER(uae_u16 w)
+{
+ if (serper == w) {
+ return;
+ }
+ serper = w;
+ const int baud = serial_period_to_baud(w);
+ if (baud > 0) {
+ setbaud(baud == 31400 ? 38400 : baud, baud);
+ }
+}
+
+uae_u16 SERDATR(void)
+{
+ uae_u16 v = serdatr & 0x03ff;
+ v |= 0x2000 | 0x1000 | 0x0800;
+ if (rx_full) {
+ v |= 0x4000;
+ }
+ if (ovrun) {
+ v |= 0x8000;
+ }
+ rx_full = false;
+ if (!rx_irq) {
+ INTREQ_INT(11, 0);
+ }
+ return v;
+}
+
+void SERDAT(uae_u16 w)
+{
+ serdat = w;
+ if (!serdev) {
+ return;
+ }
+ const int c = w & 0xff;
+ if (w & 0x100) {
+ writeser(((w >> 8) & 1) | 0xa8);
+ }
+ if (currprefs.serial_crlf && c == 10 && serial_send_previous != 13) {
+ writeser(13);
+ }
+ writeser(c);
+ serial_send_previous = (uae_u8)c;
+}
+
+void serial_rbf_change(bool set)
+{
+ ovrun = set;
+ if (!set) {
+ rx_irq = false;
+ }
+}
+
+void serial_dtr_on(void)
+{
+ dtr = true;
+ if (currprefs.serial_demand) {
+ serial_open();
+ }
+ setserstat(TIOCM_DTR, 1);
+}
+
+void serial_dtr_off(void)
+{
+ dtr = false;
+ setserstat(TIOCM_DTR, 0);
+ if (currprefs.serial_demand) {
+ serial_close();
+ }
+}
+
+uae_u8 serial_readstatus(uae_u8 v, uae_u8)
+{
+ int status = 0;
+ uae_u8 serbits = oldserbits;
+
+ if (serloop_enabled) {
+ status = TIOCM_DSR | TIOCM_CAR | TIOCM_CTS;
+ } else if (currprefs.use_serial) {
+ getserstat(&status);
+ } else {
+ return v;
+ }
+
+ if (currprefs.serial_rtsctsdtrdtecd) {
+ if (status & TIOCM_CAR) {
+ serbits &= ~0x20;
+ } else {
+ serbits |= 0x20;
+ }
+ if (status & TIOCM_DSR) {
+ serbits &= ~0x08;
+ } else {
+ serbits |= 0x08;
+ }
+ if (status & TIOCM_CTS) {
+ serbits &= ~0x10;
+ } else {
+ serbits |= 0x10;
+ }
+ }
+ if (currprefs.serial_ri) {
+ if (status & TIOCM_RI) {
+ serbits &= ~0x04;
+ } else {
+ serbits |= 0x04;
+ }
+ } else {
+ serbits &= ~0x04;
+ serbits |= v & 0x04;
+ }
+
+ serbits &= 0x04 | 0x08 | 0x10 | 0x20;
+ oldserbits &= ~(0x04 | 0x08 | 0x10 | 0x20);
+ oldserbits |= serbits;
+ return (v & (0x80 | 0x40 | 0x02 | 0x01)) | serbits;
+}
+
+uae_u8 serial_writestatus(uae_u8 newstate, uae_u8 dir)
+{
+ if (currprefs.use_serial) {
+ if (currprefs.serial_rtsctsdtrdtecd && ((oldserbits ^ newstate) & 0x80) && (dir & 0x80)) {
+ if (newstate & 0x80) {
+ serial_dtr_off();
+ } else {
+ serial_dtr_on();
+ }
+ }
+ if (!currprefs.serial_hwctsrts && currprefs.serial_rtsctsdtrdtecd && ((oldserbits ^ newstate) & 0x40) && (dir & 0x40)) {
+ setserstat(TIOCM_RTS, (newstate & 0x40) ? 0 : 1);
+ }
+ }
+ oldserbits &= ~(0x80 | 0x40);
+ oldserbits |= newstate & (0x80 | 0x40);
+ return oldserbits;
+}
+
+void serial_flush_buffer(void)
+{
+ writeser_flush();
+}
+
+void serial_rethink(void)
+{
+ checkreceive_serial();
+}
+
+void serial_hsynchandler(void)
+{
+ checkreceive_serial();
+}
+
+void serial_uartbreak(int v)
+{
+ if (serial_fd < 0) {
+ return;
+ }
+ if (v) {
+ tcsendbreak(serial_fd, 0);
+ }
+}
+
+void serial_open(void)
+{
+ if (serdev) {
+ return;
+ }
+ serper = 0;
+ if (!_tcsicmp(currprefs.sername, SERIAL_LOOPBACK)) {
+ serloop_enabled = true;
+ } else if (!currprefs.sername[0]) {
+ serempty_enabled = true;
+ } else if (!openser(currprefs.sername)) {
+ write_log(_T("SERIAL: Could not open device %s\n"), currprefs.sername);
+ return;
+ }
+ serdev = 1;
+ serdatr = 0x0100;
+}
+
+void serial_close(void)
+{
+ closeser();
+ serdev = 0;
+ serloop_enabled = false;
+ serempty_enabled = false;
+ rx_full = false;
+ rx_irq = false;
+ ovrun = false;
+}
+
+void serial_init(void)
+{
+ if (!currprefs.use_serial) {
+ return;
+ }
+ if (!currprefs.serial_demand) {
+ serial_open();
+ }
+ serdatr = 0x0100;
+}
+
+void serial_exit(void)
+{
+ serial_close();
+ dtr = false;
+ oldserbits = 0;
+ serdat = 0;
+ serdatr = 0x0100;
+}
+
+void enet_writeser(uae_u16)
+{
+}
+
+int enet_readseravail(void)
+{
+ return 0;
+}
+
+int enet_readser(uae_u16 *)
+{
+ return 0;
+}
+
+int enet_open(TCHAR *)
+{
+ return 0;
+}
+
+void enet_close(void)
+{
+}
+
+#endif
--- /dev/null
+#include "sysconfig.h"
+#include "sysdeps.h"
+
+#include "sndboard_host.h"
+#include "uae.h"
+
+#ifdef UAE_UNIX_WITH_SDL3
+
+#define SDL_MAIN_HANDLED
+#include <SDL3/SDL.h>
+#include <SDL3/SDL_main.h>
+
+static SDL_AudioStream *capture_stream;
+static SDL_AudioSpec capture_spec;
+static bool audio_initialized;
+static uae_u8 capture_buffer[8192];
+
+static bool ensure_audio(void)
+{
+ if (audio_initialized) {
+ return true;
+ }
+ SDL_SetMainReady();
+ if (!SDL_InitSubSystem(SDL_INIT_AUDIO)) {
+ write_log(_T("SDL3: sound-board capture audio unavailable: %s\n"), SDL_GetError());
+ return false;
+ }
+ audio_initialized = true;
+ return true;
+}
+
+uae_u8 *unix_sndboard_get_buffer(int *frames)
+{
+ int available;
+ int bytes;
+ int got;
+
+ if (frames) {
+ *frames = 0;
+ }
+ if (!capture_stream) {
+ return NULL;
+ }
+ available = SDL_GetAudioStreamAvailable(capture_stream);
+ bytes = available - (available % 4);
+ if (bytes <= 0) {
+ return NULL;
+ }
+ if (bytes > (int)sizeof capture_buffer) {
+ bytes = sizeof capture_buffer;
+ bytes -= bytes % 4;
+ }
+ got = SDL_GetAudioStreamData(capture_stream, capture_buffer, bytes);
+ if (got <= 0) {
+ return NULL;
+ }
+ got -= got % 4;
+ if (got <= 0) {
+ return NULL;
+ }
+ if (frames) {
+ *frames = got / 4;
+ }
+ return capture_buffer;
+}
+
+void unix_sndboard_release_buffer(uae_u8 *buffer, int frames)
+{
+}
+
+void unix_sndboard_free_capture(void)
+{
+ if (capture_stream) {
+ SDL_DestroyAudioStream(capture_stream);
+ capture_stream = NULL;
+ }
+ memset(&capture_spec, 0, sizeof capture_spec);
+}
+
+bool unix_sndboard_init_capture(int freq)
+{
+ unix_sndboard_free_capture();
+ if (!ensure_audio()) {
+ return false;
+ }
+ memset(&capture_spec, 0, sizeof capture_spec);
+ capture_spec.freq = freq > 0 ? freq : 44100;
+ capture_spec.format = SDL_AUDIO_S16;
+ capture_spec.channels = 2;
+ capture_stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_RECORDING, &capture_spec, NULL, NULL);
+ if (!capture_stream) {
+ write_log(_T("SDL3: failed to open default sound-board capture device: %s\n"), SDL_GetError());
+ return false;
+ }
+ SDL_ResumeAudioStreamDevice(capture_stream);
+ write_log(_T("SDL3: sound-board capture initialized, freq=%d\n"), capture_spec.freq);
+ return true;
+}
+
+#else
+
+uae_u8 *unix_sndboard_get_buffer(int *frames)
+{
+ if (frames) {
+ *frames = 0;
+ }
+ return NULL;
+}
+
+void unix_sndboard_release_buffer(uae_u8 *buffer, int frames)
+{
+}
+
+void unix_sndboard_free_capture(void)
+{
+}
+
+bool unix_sndboard_init_capture(int freq)
+{
+ return false;
+}
+
+#endif
--- /dev/null
+#ifndef UAE_UNIX_SNDBOARD_HOST_H
+#define UAE_UNIX_SNDBOARD_HOST_H
+
+uae_u8 *unix_sndboard_get_buffer(int *frames);
+void unix_sndboard_release_buffer(uae_u8 *buffer, int frames);
+void unix_sndboard_free_capture(void);
+bool unix_sndboard_init_capture(int freq);
+
+#endif /* UAE_UNIX_SNDBOARD_HOST_H */
--- /dev/null
+#pragma once
+
+bool target_dlopen_plugin(const TCHAR *name, TCHAR *loaded_path,
+ size_t loaded_path_size, UAE_DLHANDLE *handlep);
--- /dev/null
+#pragma once
+
+void target_main_set_args(int argc, TCHAR **argv);
+int target_main_handle_early(int argc, TCHAR **argv);
--- /dev/null
+/*
+ * UAE - The Un*x Amiga Emulator
+ *
+ * Unix uaenet packet backend
+ *
+ * Copyright 2026 WinUAE contributors
+ */
+
+#include "sysconfig.h"
+#include "sysdeps.h"
+
+#include <algorithm>
+#include <cerrno>
+#include <cstdio>
+#include <cstring>
+
+#include <fcntl.h>
+#include <ifaddrs.h>
+#include <net/if.h>
+#include <pcap/pcap.h>
+#include <pcap/dlt.h>
+#include <poll.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
+#include <net/if_dl.h>
+#include <net/if_types.h>
+#endif
+#if defined(__linux__)
+#include <net/if_arp.h>
+#include <linux/if_tun.h>
+#include <netpacket/packet.h>
+#endif
+
+#include "options.h"
+#include "sana2.h"
+#include "threaddep/thread.h"
+#include "uaenet.h"
+#include "uae.h"
+
+int log_ethernet;
+
+static struct netdriverdata tds[MAX_TOTAL_NET_DEVICES];
+static int enumerated;
+static int enumerated_count;
+static int ethernet_paused;
+
+static const uae_u8 uaemac[] = { 0xaa, 0x82, 0x8a, 0x00, 0x00, 0x00 };
+
+enum
+{
+ UAENET_BACKEND_NONE,
+ UAENET_BACKEND_PCAP,
+ UAENET_BACKEND_TAP,
+ UAENET_BACKEND_TUN
+};
+
+struct uaenetdataunix
+{
+ void *user;
+ struct netdriverdata *tc;
+ uae_u8 *readbuffer;
+ uae_u8 *writebuffer;
+ int mtu;
+
+ char errbuf[PCAP_ERRBUF_SIZE];
+ pcap_t *fp;
+ int backend;
+ int fd;
+ uaenet_gotfunc *gotfunc;
+ uaenet_getfunc *getfunc;
+
+ volatile int threadactiver;
+ uae_thread_id tidr;
+ uae_sem_t sync_semr;
+ volatile int threadactivew;
+ uae_thread_id tidw;
+ uae_sem_t sync_semw;
+ uae_sem_t change_sem;
+ uae_sem_t write_sem;
+};
+
+int uaenet_getdatalenght(void)
+{
+ return sizeof(struct uaenetdataunix);
+}
+
+static void uaenet_initdata(struct uaenetdataunix *sd, void *user)
+{
+ memset(sd, 0, sizeof(*sd));
+ sd->user = user;
+ sd->fd = -1;
+}
+
+static uae_u16 get_be16(const uae_u8 *p)
+{
+ return ((uae_u16)p[0] << 8) | p[1];
+}
+
+static void put_be16(uae_u8 *p, uae_u16 v)
+{
+ p[0] = v >> 8;
+ p[1] = v;
+}
+
+static bool name_has_prefix(const char *name, const char *prefix)
+{
+ return name && !strncmp(name, prefix, strlen(prefix));
+}
+
+static bool tname_has_prefix(const TCHAR *name, const TCHAR *prefix)
+{
+ return name && !_tcsncmp(name, prefix, _tcslen(prefix));
+}
+
+static const char *tuntap_interface_name(const char *name)
+{
+ const char *p = strchr(name, ':');
+ return p ? p + 1 : name;
+}
+
+static bool uaenet_is_tuntap_name(const TCHAR *name)
+{
+ return tname_has_prefix(name, _T("tap:")) || tname_has_prefix(name, _T("tun:"));
+}
+
+static void make_fallback_macs(struct netdriverdata *tc, int index)
+{
+ static const uae_u8 hostmac[] = { 0xaa, 0x82, 0x8a, 0xfe, 0xfe, 0x00 };
+ memcpy(tc->originalmac, hostmac, 6);
+ memcpy(tc->mac, uaemac, 6);
+ tc->originalmac[5] = (uae_u8)(index + 1);
+ tc->mac[5] = (uae_u8)(index + 1);
+}
+
+static bool write_fd_packet(struct uaenetdataunix *sd, const uae_u8 *data, int len)
+{
+ const uae_u8 *src = data;
+ int outlen = len;
+
+ if (sd->backend == UAENET_BACKEND_TUN) {
+ if (len < 14) {
+ return true;
+ }
+ const uae_u16 ethertype = get_be16(data + 12);
+ if (ethertype == 0x0800 || ethertype == 0x86dd) {
+ src = data + 14;
+ outlen = len - 14;
+ } else {
+ return true;
+ }
+ }
+
+ while (outlen > 0) {
+ const ssize_t done = write(sd->fd, src, outlen);
+ if (done < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ return false;
+ }
+ write_log(_T("uaenet: TAP/TUN write failed: %s\n"), strerror(errno));
+ return false;
+ }
+ if (done == 0) {
+ return false;
+ }
+ src += done;
+ outlen -= done;
+ }
+ return true;
+}
+
+static bool reply_tun_arp(struct uaenetdataunix *sd, const uae_u8 *data, int len)
+{
+ if (len < 42 || get_be16(data + 12) != 0x0806) {
+ return false;
+ }
+
+ const uae_u8 *arp = data + 14;
+ if (get_be16(arp + 0) != 1 || get_be16(arp + 2) != 0x0800 ||
+ arp[4] != 6 || arp[5] != 4 || get_be16(arp + 6) != 1) {
+ return false;
+ }
+
+ uae_u8 reply[42];
+ memcpy(reply + 0, data + 6, 6);
+ memcpy(reply + 6, sd->tc->originalmac, 6);
+ put_be16(reply + 12, 0x0806);
+ put_be16(reply + 14, 1);
+ put_be16(reply + 16, 0x0800);
+ reply[18] = 6;
+ reply[19] = 4;
+ put_be16(reply + 20, 2);
+ memcpy(reply + 22, sd->tc->originalmac, 6);
+ memcpy(reply + 28, arp + 24, 4);
+ memcpy(reply + 32, arp + 8, 6);
+ memcpy(reply + 38, arp + 14, 4);
+
+ if (!ethernet_paused) {
+ sd->gotfunc((struct s2devstruct*)sd->user, reply, sizeof(reply));
+ }
+ return true;
+}
+
+static bool send_packet(struct uaenetdataunix *sd, const uae_u8 *data, int len)
+{
+ if (sd->backend == UAENET_BACKEND_PCAP) {
+ if (pcap_sendpacket(sd->fp, data, len) < 0) {
+ TCHAR *err = au(pcap_geterr(sd->fp));
+ write_log(_T("uaenet: pcap_sendpacket failed: %s\n"), err);
+ xfree(err);
+ return false;
+ }
+ return true;
+ }
+
+ if (sd->backend == UAENET_BACKEND_TUN && reply_tun_arp(sd, data, len)) {
+ return true;
+ }
+
+ return write_fd_packet(sd, data, len);
+}
+
+static void uaenet_trap_threadr(void *arg)
+{
+ struct uaenetdataunix *sd = (struct uaenetdataunix*)arg;
+
+ uae_set_thread_priority(NULL, 1);
+ sd->threadactiver = 1;
+ uae_sem_post(&sd->sync_semr);
+
+ while (sd->threadactiver == 1) {
+ if (sd->backend == UAENET_BACKEND_PCAP) {
+ struct pcap_pkthdr *header = NULL;
+ const u_char *pkt_data = NULL;
+ const int r = pcap_next_ex(sd->fp, &header, &pkt_data);
+ if (r == 1 && header && pkt_data && !ethernet_paused) {
+ const int len = std::min<int>((int)header->caplen, (int)header->len);
+ if (len > 0) {
+ uae_sem_wait(&sd->change_sem);
+ sd->gotfunc((struct s2devstruct*)sd->user, pkt_data, len);
+ uae_sem_post(&sd->change_sem);
+ }
+ } else if (r == 0) {
+ continue;
+ } else if (r == PCAP_ERROR_BREAK && sd->threadactiver != 1) {
+ break;
+ } else if (r < 0) {
+ write_log(_T("uaenet: pcap_next_ex failed, err=%d\n"), r);
+ break;
+ }
+ } else {
+ struct pollfd pfd;
+ pfd.fd = sd->fd;
+ pfd.events = POLLIN;
+ pfd.revents = 0;
+ const int pr = poll(&pfd, 1, 100);
+ if (pr < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+ write_log(_T("uaenet: TAP/TUN poll failed: %s\n"), strerror(errno));
+ break;
+ }
+ if (pr == 0 || !(pfd.revents & POLLIN)) {
+ continue;
+ }
+
+ uae_u8 *pkt_data = sd->readbuffer;
+ ssize_t got;
+ if (sd->backend == UAENET_BACKEND_TUN) {
+ got = read(sd->fd, sd->readbuffer + 14, sd->mtu);
+ if (got > 0) {
+ const uae_u8 version = sd->readbuffer[14] >> 4;
+ if (version == 4 || version == 6) {
+ memcpy(sd->readbuffer + 0, sd->tc->mac, 6);
+ memcpy(sd->readbuffer + 6, sd->tc->originalmac, 6);
+ put_be16(sd->readbuffer + 12, version == 4 ? 0x0800 : 0x86dd);
+ got += 14;
+ } else {
+ got = 0;
+ }
+ }
+ } else {
+ got = read(sd->fd, sd->readbuffer, sd->mtu + 64);
+ }
+ if (got < 0) {
+ if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) {
+ continue;
+ }
+ write_log(_T("uaenet: TAP/TUN read failed: %s\n"), strerror(errno));
+ break;
+ }
+ if (got > 0 && !ethernet_paused) {
+ uae_sem_wait(&sd->change_sem);
+ sd->gotfunc((struct s2devstruct*)sd->user, pkt_data, (int)got);
+ uae_sem_post(&sd->change_sem);
+ }
+ }
+ }
+
+ sd->threadactiver = 0;
+ uae_sem_post(&sd->sync_semr);
+}
+
+static void uaenet_trap_threadw(void *arg)
+{
+ struct uaenetdataunix *sd = (struct uaenetdataunix*)arg;
+
+ uae_set_thread_priority(NULL, 1);
+ sd->threadactivew = 1;
+ uae_sem_post(&sd->sync_semw);
+
+ while (sd->threadactivew == 1) {
+ int towrite = sd->mtu;
+ bool wrote = false;
+
+ uae_sem_wait(&sd->change_sem);
+ if (sd->getfunc((struct s2devstruct*)sd->user, sd->writebuffer, &towrite)) {
+ if (log_ethernet & 1) {
+ TCHAR out[1600 * 2], *p = out;
+ for (int i = 0; i < towrite && i < 1600; i++) {
+ _stprintf(p, _T("%02x"), sd->writebuffer[i]);
+ p += 2;
+ *p = 0;
+ }
+ write_log(_T("OUT %4d: %s\n"), towrite, out);
+ }
+ send_packet(sd, sd->writebuffer, towrite);
+ wrote = true;
+ }
+ uae_sem_post(&sd->change_sem);
+
+ if (!wrote) {
+ uae_sem_trywait_delay(&sd->write_sem, 100);
+ }
+ }
+
+ sd->threadactivew = 0;
+ uae_sem_post(&sd->sync_semw);
+}
+
+void uaenet_trigger(void *vsd)
+{
+ struct uaenetdataunix *sd = (struct uaenetdataunix*)vsd;
+ if (sd) {
+ uae_sem_post(&sd->write_sem);
+ }
+}
+
+static bool get_interface_mac_2(const char *name, uae_u8 *mac, bool ethernet_only)
+{
+ struct ifaddrs *ifaddr = NULL;
+ bool found = false;
+
+ if (getifaddrs(&ifaddr) != 0) {
+ return false;
+ }
+
+ for (const struct ifaddrs *ifa = ifaddr; ifa; ifa = ifa->ifa_next) {
+ if (!ifa->ifa_name || !ifa->ifa_addr || strcmp(ifa->ifa_name, name) != 0) {
+ continue;
+ }
+#if defined(__linux__)
+ if (ifa->ifa_addr->sa_family == AF_PACKET) {
+ const struct sockaddr_ll *sll = (const struct sockaddr_ll*)ifa->ifa_addr;
+ if (sll->sll_halen >= 6 && (!ethernet_only || sll->sll_hatype == ARPHRD_ETHER)) {
+ memcpy(mac, sll->sll_addr, 6);
+ found = true;
+ break;
+ }
+ }
+#elif defined(AF_LINK)
+ if (ifa->ifa_addr->sa_family == AF_LINK) {
+ const struct sockaddr_dl *sdl = (const struct sockaddr_dl*)ifa->ifa_addr;
+ if (sdl->sdl_alen >= 6 && (!ethernet_only || sdl->sdl_type == IFT_ETHER)) {
+ memcpy(mac, LLADDR(sdl), 6);
+ found = true;
+ break;
+ }
+ }
+#endif
+ }
+
+ freeifaddrs(ifaddr);
+ return found;
+}
+
+static bool get_interface_mac(const char *name, uae_u8 *mac)
+{
+ return get_interface_mac_2(name, mac, false);
+}
+
+static bool get_ethernet_interface_mac(const char *name, uae_u8 *mac)
+{
+ return get_interface_mac_2(name, mac, true);
+}
+
+static bool set_nonblocking(int fd)
+{
+ int flags = fcntl(fd, F_GETFL, 0);
+ if (flags < 0) {
+ return false;
+ }
+ return fcntl(fd, F_SETFL, flags | O_NONBLOCK) == 0;
+}
+
+static int open_tuntap_device(struct netdriverdata *tc, const char *name)
+{
+ const char *ifname = tuntap_interface_name(name);
+ const bool istun = tc->type == UAENET_TUN;
+
+#if defined(__linux__)
+ int fd = open("/dev/net/tun", O_RDWR);
+ if (fd < 0) {
+ write_log(_T("uaenet: failed to open /dev/net/tun: %s\n"), strerror(errno));
+ return -1;
+ }
+
+ struct ifreq ifr;
+ memset(&ifr, 0, sizeof(ifr));
+ ifr.ifr_flags = (istun ? IFF_TUN : IFF_TAP) | IFF_NO_PI;
+ if (ifname && ifname[0] && strcmp(ifname, "auto")) {
+ strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1);
+ }
+ if (ioctl(fd, TUNSETIFF, (void*)&ifr) < 0) {
+ write_log(_T("uaenet: TUNSETIFF '%s' failed: %s\n"), ifname, strerror(errno));
+ close(fd);
+ return -1;
+ }
+ if (!set_nonblocking(fd)) {
+ write_log(_T("uaenet: failed to set '%s' non-blocking: %s\n"), ifr.ifr_name, strerror(errno));
+ }
+ return fd;
+#else
+ char path[256];
+ if (name_has_prefix(ifname, "/dev/")) {
+ snprintf(path, sizeof(path), "%s", ifname);
+ } else {
+ snprintf(path, sizeof(path), "/dev/%s", ifname);
+ }
+ int fd = open(path, O_RDWR);
+ if (fd < 0) {
+ write_log(_T("uaenet: failed to open %s: %s\n"), path, strerror(errno));
+ return -1;
+ }
+ if (!set_nonblocking(fd)) {
+ write_log(_T("uaenet: failed to set '%s' non-blocking: %s\n"), path, strerror(errno));
+ }
+ return fd;
+#endif
+}
+
+int uaenet_open(void *vsd, struct netdriverdata *tc, void *user, uaenet_gotfunc *gotfunc, uaenet_getfunc *getfunc, int promiscuous, const uae_u8 *mac)
+{
+ struct uaenetdataunix *sd = (struct uaenetdataunix*)vsd;
+ char *name;
+
+ uaenet_initdata(sd, user);
+ name = ua(tc->name);
+ if (mac) {
+ memcpy(tc->mac, mac, 6);
+ }
+ if (memcmp(tc->mac, tc->originalmac, 6) != 0) {
+ promiscuous = 1;
+ }
+
+ if (tc->type == UAENET_PCAP) {
+ sd->backend = UAENET_BACKEND_PCAP;
+ sd->fp = pcap_open_live(name, 65536, promiscuous ? 1 : 0, 100, sd->errbuf);
+ xfree(name);
+ if (!sd->fp) {
+ TCHAR *err = au(sd->errbuf);
+ write_log(_T("uaenet: '%s' failed to open: %s\n"), tc->name, err);
+ xfree(err);
+ return 0;
+ }
+
+ if (pcap_datalink(sd->fp) != DLT_EN10MB) {
+ write_log(_T("uaenet: '%s' is not an Ethernet adapter\n"), tc->name);
+ uaenet_close(sd);
+ return 0;
+ }
+ } else if (tc->type == UAENET_TAP || tc->type == UAENET_TUN) {
+ sd->backend = tc->type == UAENET_TAP ? UAENET_BACKEND_TAP : UAENET_BACKEND_TUN;
+ sd->fd = open_tuntap_device(tc, name);
+ xfree(name);
+ if (sd->fd < 0) {
+ return 0;
+ }
+ } else {
+ xfree(name);
+ return 0;
+ }
+
+ sd->tc = tc;
+ sd->user = user;
+ sd->mtu = tc->mtu > 0 ? tc->mtu : 1522;
+ sd->readbuffer = xmalloc(uae_u8, sd->mtu + 64);
+ sd->writebuffer = xmalloc(uae_u8, sd->mtu + 64);
+ sd->gotfunc = gotfunc;
+ sd->getfunc = getfunc;
+
+ uae_sem_init(&sd->change_sem, 0, 1);
+ uae_sem_init(&sd->write_sem, 0, 0);
+ uae_sem_init(&sd->sync_semr, 0, 0);
+ if (!uae_start_thread(_T("uaenet_unixr"), uaenet_trap_threadr, sd, &sd->tidr)) {
+ goto end;
+ }
+ uae_sem_wait(&sd->sync_semr);
+
+ uae_sem_init(&sd->sync_semw, 0, 0);
+ if (!uae_start_thread(_T("uaenet_unixw"), uaenet_trap_threadw, sd, &sd->tidw)) {
+ goto end;
+ }
+ uae_sem_wait(&sd->sync_semw);
+
+ write_log(_T("uaenet_unix initialized (%s)\n"),
+ sd->backend == UAENET_BACKEND_PCAP ? _T("pcap") : (sd->backend == UAENET_BACKEND_TAP ? _T("tap") : _T("tun")));
+ return 1;
+
+end:
+ uaenet_close(sd);
+ return 0;
+}
+
+void uaenet_close(void *vsd)
+{
+ struct uaenetdataunix *sd = (struct uaenetdataunix*)vsd;
+ if (!sd) {
+ return;
+ }
+
+ if (sd->threadactiver) {
+ sd->threadactiver = -1;
+ if (sd->fp) {
+ pcap_breakloop(sd->fp);
+ }
+ uae_wait_thread(sd->tidr);
+ write_log(_T("uaenet_unix read thread stopped\n"));
+ }
+ if (sd->threadactivew) {
+ sd->threadactivew = -1;
+ uae_sem_post(&sd->write_sem);
+ uae_wait_thread(sd->tidw);
+ write_log(_T("uaenet_unix write thread stopped\n"));
+ }
+
+ uae_sem_destroy(&sd->sync_semr);
+ uae_sem_destroy(&sd->sync_semw);
+ uae_sem_destroy(&sd->change_sem);
+ uae_sem_destroy(&sd->write_sem);
+
+ xfree(sd->readbuffer);
+ xfree(sd->writebuffer);
+ if (sd->fp) {
+ pcap_close(sd->fp);
+ }
+ if (sd->fd >= 0) {
+ close(sd->fd);
+ }
+ uaenet_initdata(sd, sd->user);
+ write_log(_T("uaenet_unix closed\n"));
+}
+
+static bool has_enumerated_name(const TCHAR *name)
+{
+ for (int i = 0; i < MAX_TOTAL_NET_DEVICES; i++) {
+ if (tds[i].active && tds[i].name && !_tcsicmp(tds[i].name, name)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static struct netdriverdata *add_pcap_entry(int *cntp, const char *ifname, const char *desc, bool verified)
+{
+ if (!ifname || !ifname[0] || *cntp >= MAX_TOTAL_NET_DEVICES) {
+ return NULL;
+ }
+
+ TCHAR *tname = au(ifname);
+ if (has_enumerated_name(tname)) {
+ xfree(tname);
+ return NULL;
+ }
+
+ char dbuf[512];
+ if (verified) {
+ snprintf(dbuf, sizeof(dbuf), "%s", desc ? desc : ifname);
+ } else {
+ snprintf(dbuf, sizeof(dbuf), "%s (pcap, permission needed)", desc ? desc : ifname);
+ }
+
+ TCHAR *tdesc = au(dbuf);
+ struct netdriverdata *tc = &tds[*cntp];
+ memset(tc, 0, sizeof(*tc));
+ memcpy(tc->mac, uaemac, 6);
+ if ((verified ? get_interface_mac : get_ethernet_interface_mac)(ifname, tc->mac)) {
+ memcpy(tc->originalmac, tc->mac, 6);
+ } else {
+ if (!verified) {
+ xfree(tname);
+ xfree(tdesc);
+ return NULL;
+ }
+ make_fallback_macs(tc, *cntp);
+ }
+
+ write_log(_T("- MAC %02X:%02X:%02X:%02X:%02X:%02X -> %02X:%02X:%02X:%02X:%02X:%02X\n"),
+ tc->originalmac[0], tc->originalmac[1], tc->originalmac[2], tc->originalmac[3], tc->originalmac[4], tc->originalmac[5],
+ uaemac[0], uaemac[1], uaemac[2], tc->originalmac[3], tc->originalmac[4], tc->originalmac[5]);
+ memcpy(tc->mac, uaemac, 3);
+ tc->mac[3] = tc->originalmac[3];
+ tc->mac[4] = tc->originalmac[4];
+ tc->mac[5] = tc->originalmac[5];
+ tc->type = UAENET_PCAP;
+ tc->active = 1;
+ tc->mtu = 1522;
+ tc->name = tname;
+ tc->desc = tdesc;
+ (*cntp)++;
+ return tc;
+}
+
+static struct netdriverdata *add_tuntap_entry(int *cntp, int type, const char *ifname)
+{
+ if (!ifname || !ifname[0] || *cntp >= MAX_TOTAL_NET_DEVICES) {
+ return NULL;
+ }
+
+ char nbuf[256];
+ char dbuf[320];
+ snprintf(nbuf, sizeof(nbuf), "%s:%s", type == UAENET_TAP ? "tap" : "tun", ifname);
+ snprintf(dbuf, sizeof(dbuf), "%s interface %s (direct)", type == UAENET_TAP ? "TAP" : "TUN", ifname);
+
+ TCHAR *tname = au(nbuf);
+ if (has_enumerated_name(tname)) {
+ xfree(tname);
+ return NULL;
+ }
+
+ TCHAR *tdesc = au(dbuf);
+ struct netdriverdata *tc = &tds[*cntp];
+ memset(tc, 0, sizeof(*tc));
+ make_fallback_macs(tc, *cntp);
+ if (type == UAENET_TAP && get_interface_mac(ifname, tc->originalmac)) {
+ memcpy(tc->mac, tc->originalmac, 6);
+ memcpy(tc->mac, uaemac, 3);
+ }
+ tc->type = type;
+ tc->active = 1;
+ tc->mtu = 1500;
+ tc->name = tname;
+ tc->desc = tdesc;
+ (*cntp)++;
+ return tc;
+}
+
+static struct netdriverdata *add_tuntap_config_entry(const TCHAR *name)
+{
+ char *aname;
+ struct netdriverdata *tc;
+
+ if (!uaenet_is_tuntap_name(name) || enumerated_count >= MAX_TOTAL_NET_DEVICES) {
+ return NULL;
+ }
+
+ aname = ua(name);
+ const int type = name_has_prefix(aname, "tun:") ? UAENET_TUN : UAENET_TAP;
+ const char *ifname = tuntap_interface_name(aname);
+ int cnt = enumerated_count;
+ tc = add_tuntap_entry(&cnt, type, ifname);
+ enumerated_count = cnt;
+ xfree(aname);
+ return tc;
+}
+
+static int enumerate_tuntap_interfaces(int cnt)
+{
+#if defined(__linux__)
+ struct ifaddrs *ifaddr = NULL;
+ if (access("/dev/net/tun", F_OK) != 0) {
+ return cnt;
+ }
+ if (getifaddrs(&ifaddr) != 0) {
+ return cnt;
+ }
+ for (const struct ifaddrs *ifa = ifaddr; ifa && cnt < MAX_TOTAL_NET_DEVICES; ifa = ifa->ifa_next) {
+ if (!ifa->ifa_name || !ifa->ifa_addr || ifa->ifa_addr->sa_family != AF_PACKET) {
+ continue;
+ }
+ int type = 0;
+ if (name_has_prefix(ifa->ifa_name, "tap")) {
+ type = UAENET_TAP;
+ }
+ if (name_has_prefix(ifa->ifa_name, "tun")) {
+ type = UAENET_TUN;
+ }
+ if (type) {
+ add_tuntap_entry(&cnt, type, ifa->ifa_name);
+ }
+ }
+ freeifaddrs(ifaddr);
+#else
+ for (int i = 0; i < 32 && cnt < MAX_TOTAL_NET_DEVICES; i++) {
+ char path[64];
+ char ifname[32];
+ snprintf(path, sizeof(path), "/dev/tap%d", i);
+ if (access(path, F_OK) == 0) {
+ snprintf(ifname, sizeof(ifname), "tap%d", i);
+ add_tuntap_entry(&cnt, UAENET_TAP, ifname);
+ }
+ snprintf(path, sizeof(path), "/dev/tun%d", i);
+ if (access(path, F_OK) == 0) {
+ snprintf(ifname, sizeof(ifname), "tun%d", i);
+ add_tuntap_entry(&cnt, UAENET_TUN, ifname);
+ }
+ }
+#endif
+ return cnt;
+}
+
+void uaenet_enumerate_free(void)
+{
+ for (int i = 0; i < MAX_TOTAL_NET_DEVICES; i++) {
+ if (tds[i].name) {
+ xfree((void*)tds[i].name);
+ }
+ if (tds[i].desc) {
+ xfree((void*)tds[i].desc);
+ }
+ tds[i].active = 0;
+ tds[i].name = NULL;
+ tds[i].desc = NULL;
+ }
+ enumerated = 0;
+ enumerated_count = 0;
+}
+
+static struct netdriverdata *enumit(const TCHAR *name)
+{
+ if (!name) {
+ return tds;
+ }
+ for (int i = 0; i < enumerated_count; i++) {
+ TCHAR mac[20];
+ struct netdriverdata *tc = &tds[i];
+ _stprintf(mac, _T("%02X:%02X:%02X:%02X:%02X:%02X"),
+ tc->mac[0], tc->mac[1], tc->mac[2], tc->mac[3], tc->mac[4], tc->mac[5]);
+ if (tc->active && (!_tcsicmp(name, tc->name) || !_tcsicmp(name, mac))) {
+ return tc;
+ }
+ }
+ if (uaenet_is_tuntap_name(name)) {
+ return add_tuntap_config_entry(name);
+ }
+ return NULL;
+}
+
+struct netdriverdata *uaenet_enumerate(const TCHAR *name)
+{
+ char errbuf[PCAP_ERRBUF_SIZE];
+ pcap_if_t *alldevs = NULL;
+ int cnt = 0;
+
+ if (enumerated) {
+ return enumit(name);
+ }
+
+ if (pcap_findalldevs(&alldevs, errbuf) < 0) {
+ TCHAR *err = au(errbuf);
+ write_log(_T("uaenet: failed to get interfaces: %s\n"), err);
+ xfree(err);
+ } else {
+ if (pcap_lib_version()) {
+ TCHAR *version = au(pcap_lib_version());
+ write_log(_T("uaenet: %s\n"), version);
+ xfree(version);
+ } else {
+ write_log(_T("uaenet: libpcap\n"));
+ }
+ write_log(_T("uaenet: detecting interfaces\n"));
+
+ for (pcap_if_t *d = alldevs; d && cnt < MAX_TOTAL_NET_DEVICES; d = d->next) {
+ pcap_t *fp;
+ char openerr[PCAP_ERRBUF_SIZE];
+ const char *desc = d->description ? d->description : d->name;
+ TCHAR *tname = au(d->name);
+ TCHAR *tdesc = au(desc);
+
+ write_log(_T("%s\n- %s\n"), tname, tdesc);
+
+ fp = pcap_open_live(d->name, 65536, 0, 0, openerr);
+ if (!fp) {
+ TCHAR *err = au(openerr);
+ write_log(_T("- pcap_open_live() failed: %s\n"), err);
+ xfree(err);
+ xfree(tname);
+ xfree(tdesc);
+ add_pcap_entry(&cnt, d->name, desc, false);
+ continue;
+ }
+ const int datalink = pcap_datalink(fp);
+ pcap_close(fp);
+ if (datalink != DLT_EN10MB) {
+ write_log(_T("- not an Ethernet adapter (%d)\n"), datalink);
+ xfree(tname);
+ xfree(tdesc);
+ continue;
+ }
+
+ if (add_pcap_entry(&cnt, d->name, desc, true)) {
+ tname = NULL;
+ tdesc = NULL;
+ }
+ xfree(tname);
+ xfree(tdesc);
+ }
+ pcap_freealldevs(alldevs);
+ }
+
+ cnt = enumerate_tuntap_interfaces(cnt);
+ enumerated_count = cnt;
+ write_log(_T("uaenet: end of detection, %d devices found.\n"), cnt);
+ enumerated = 1;
+ return enumit(name);
+}
+
+void uaenet_close_driver(struct netdriverdata *tc)
+{
+ if (!tc) {
+ return;
+ }
+ for (int i = 0; i < MAX_TOTAL_NET_DEVICES; i++) {
+ tds[i].active = 0;
+ }
+}
+
+void ethernet_pause(int pause)
+{
+ ethernet_paused = pause;
+}
+
+void ethernet_reset(void)
+{
+ ethernet_paused = 0;
+}
--- /dev/null
+#include "sysconfig.h"
+#include "sysdeps.h"
+
+#ifdef UAESERIAL
+
+#include <netdb.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <sys/ioctl.h>
+#include <termios.h>
+
+#include "options.h"
+#include "serial.h"
+#include "threaddep/thread.h"
+#include "uaeserial_unix.h"
+#include "uae.h"
+
+struct uaeserialdataunix
+{
+ int fd;
+ int listen_fd;
+ int conn_fd;
+ bool tcpserial;
+ bool telnet_iac;
+ int telnet_skip;
+ bool saved_tios_valid;
+ struct termios saved_tios;
+ volatile int threadactive;
+ uae_sem_t sync_sem;
+ void *user;
+ int unit;
+ pthread_mutex_t lock;
+ bool lock_valid;
+ uae_u8 rxbuf[8192];
+ int rxbuf_len;
+};
+
+static void close_fd(int *fd)
+{
+ if (*fd >= 0) {
+ close(*fd);
+ *fd = -1;
+ }
+}
+
+static void set_nonblock(int fd)
+{
+ int flags = fcntl(fd, F_GETFL, 0);
+ if (flags >= 0) {
+ fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+ }
+}
+
+static speed_t baud_to_speed(int baud)
+{
+ switch (baud) {
+ case 300: return B300;
+ case 1200: return B1200;
+ case 2400: return B2400;
+ case 4800: return B4800;
+ case 9600: return B9600;
+ case 19200: return B19200;
+ case 38400: return B38400;
+ case 57600: return B57600;
+ case 115200: return B115200;
+#ifdef B230400
+ case 230400: return B230400;
+#endif
+ default: return B9600;
+ }
+}
+
+static bool parse_tcp_spec(const TCHAR *spec, std::string *host, std::string *port, bool *waitmode)
+{
+ std::string s = spec ? spec : "";
+ *waitmode = false;
+ if (s.compare(0, 2, "//") == 0) {
+ s.erase(0, 2);
+ }
+ size_t slash = s.find('/');
+ if (slash != std::string::npos) {
+ std::string opt = s.substr(slash + 1);
+ s.erase(slash);
+ if (!strcasecmp(opt.c_str(), "wait")) {
+ *waitmode = true;
+ }
+ }
+ *host = "127.0.0.1";
+ *port = "1234";
+ if (s.empty()) {
+ return true;
+ }
+ size_t colon = s.rfind(':');
+ if (colon == std::string::npos) {
+ bool digits = true;
+ for (char c : s) {
+ if (!isdigit((unsigned char)c)) {
+ digits = false;
+ break;
+ }
+ }
+ if (digits) {
+ *port = s;
+ } else {
+ *host = s;
+ }
+ return true;
+ }
+ if (colon > 0) {
+ *host = s.substr(0, colon);
+ }
+ if (colon + 1 < s.size()) {
+ *port = s.substr(colon + 1);
+ }
+ return true;
+}
+
+static bool tcp_accept_pending(struct uaeserialdataunix *sd)
+{
+ if (!sd->tcpserial || sd->conn_fd >= 0 || sd->listen_fd < 0) {
+ return sd->conn_fd >= 0;
+ }
+ struct sockaddr_storage ss;
+ socklen_t sslen = sizeof ss;
+ int fd = accept(sd->listen_fd, (struct sockaddr *)&ss, &sslen);
+ if (fd < 0) {
+ return false;
+ }
+ set_nonblock(fd);
+ int one = 1;
+ setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof one);
+ sd->conn_fd = fd;
+ sd->telnet_iac = false;
+ sd->telnet_skip = 0;
+ write_log(_T("UAESER_TCP: connection accepted\n"));
+ return true;
+}
+
+static void tcp_disconnect(struct uaeserialdataunix *sd)
+{
+ if (sd->conn_fd >= 0) {
+ close_fd(&sd->conn_fd);
+ write_log(_T("UAESER_TCP: disconnect\n"));
+ }
+}
+
+static int opentcp(struct uaeserialdataunix *sd, const TCHAR *sername)
+{
+ std::string host;
+ std::string port;
+ bool waitmode = false;
+ parse_tcp_spec(sername, &host, &port, &waitmode);
+
+ struct addrinfo hints;
+ memset(&hints, 0, sizeof hints);
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_PASSIVE;
+ struct addrinfo *res = NULL;
+ int err = getaddrinfo(host.c_str(), port.c_str(), &hints, &res);
+ if (err) {
+ write_log(_T("UAESER_TCP: getaddrinfo(%s:%s) failed: %s\n"), host.c_str(), port.c_str(), gai_strerror(err));
+ return 0;
+ }
+ for (struct addrinfo *ai = res; ai; ai = ai->ai_next) {
+ sd->listen_fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+ if (sd->listen_fd < 0) {
+ continue;
+ }
+ int one = 1;
+ setsockopt(sd->listen_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one);
+ if (bind(sd->listen_fd, ai->ai_addr, ai->ai_addrlen) == 0 && listen(sd->listen_fd, 1) == 0) {
+ break;
+ }
+ close_fd(&sd->listen_fd);
+ }
+ freeaddrinfo(res);
+ if (sd->listen_fd < 0) {
+ write_log(_T("UAESER_TCP: failed to listen on %s:%s: %s\n"), host.c_str(), port.c_str(), strerror(errno));
+ return 0;
+ }
+ set_nonblock(sd->listen_fd);
+ sd->tcpserial = true;
+ write_log(_T("UAESER_TCP: listening on %s:%s\n"), host.c_str(), port.c_str());
+ while (waitmode && !tcp_accept_pending(sd)) {
+ Sleep(1000);
+ write_log(_T("UAESER_TCP: waiting for connect...\n"));
+ }
+ return 1;
+}
+
+static int configure_serial_fd(struct uaeserialdataunix *sd, int baud, int bits, int sbits, int rtscts, int parity, uae_u32 xonxoff)
+{
+ if (sd->tcpserial || sd->fd < 0) {
+ return 0;
+ }
+ struct termios tios;
+ if (tcgetattr(sd->fd, &tios) < 0) {
+ write_log(_T("UAESER: tcgetattr failed: %s\n"), strerror(errno));
+ return 5;
+ }
+ if (!sd->saved_tios_valid) {
+ sd->saved_tios = tios;
+ sd->saved_tios_valid = true;
+ }
+ cfmakeraw(&tios);
+ speed_t speed = baud_to_speed(baud > 0 ? baud : 9600);
+ cfsetispeed(&tios, speed);
+ cfsetospeed(&tios, speed);
+ tios.c_cflag |= CLOCAL | CREAD;
+ tios.c_cflag &= ~(CSIZE | CSTOPB | PARENB | PARODD);
+ tios.c_cflag |= bits == 7 ? CS7 : CS8;
+ if (sbits == 2) {
+ tios.c_cflag |= CSTOPB;
+ }
+ if (parity == 1 || parity == 2) {
+ tios.c_cflag |= PARENB;
+ if (parity == 1) {
+ tios.c_cflag |= PARODD;
+ }
+ } else if (parity == 3 || parity == 4) {
+#ifdef CMSPAR
+ tios.c_cflag |= PARENB | CMSPAR;
+ if (parity == 3) {
+ tios.c_cflag |= PARODD;
+ }
+#else
+ write_log(_T("UAESER: mark/space parity is not supported by this host termios\n"));
+ return 5;
+#endif
+ }
+#ifdef CRTSCTS
+ if (rtscts) {
+ tios.c_cflag |= CRTSCTS;
+ } else {
+ tios.c_cflag &= ~CRTSCTS;
+ }
+#endif
+ if (xonxoff & 1) {
+ tios.c_iflag |= IXON | IXOFF;
+ tios.c_cc[VSTART] = (xonxoff >> 8) & 0xff;
+ tios.c_cc[VSTOP] = (xonxoff >> 16) & 0xff;
+ } else {
+ tios.c_iflag &= ~(IXON | IXOFF);
+ }
+ if (tcsetattr(sd->fd, TCSANOW, &tios) < 0) {
+ write_log(_T("UAESER: tcsetattr failed: %s\n"), strerror(errno));
+ return 5;
+ }
+ return 0;
+}
+
+static void append_rx_byte(struct uaeserialdataunix *sd, uae_u8 v)
+{
+ if (sd->rxbuf_len < (int)sizeof(sd->rxbuf)) {
+ sd->rxbuf[sd->rxbuf_len++] = v;
+ }
+}
+
+static void poll_host_bytes(struct uaeserialdataunix *sd)
+{
+ if (sd->tcpserial) {
+ if (!tcp_accept_pending(sd)) {
+ return;
+ }
+ while (sd->rxbuf_len < (int)sizeof(sd->rxbuf)) {
+ unsigned char c;
+ ssize_t got = recv(sd->conn_fd, &c, 1, 0);
+ if (got != 1) {
+ if (got == 0 || (errno != EAGAIN && errno != EWOULDBLOCK)) {
+ tcp_disconnect(sd);
+ }
+ return;
+ }
+ if (sd->telnet_skip > 0) {
+ sd->telnet_skip--;
+ continue;
+ }
+ if (sd->telnet_iac) {
+ sd->telnet_iac = false;
+ if (c == 255) {
+ append_rx_byte(sd, c);
+ continue;
+ }
+ if (c == 251 || c == 252 || c == 253 || c == 254) {
+ sd->telnet_skip = 1;
+ }
+ continue;
+ }
+ if (c == 255) {
+ sd->telnet_iac = true;
+ continue;
+ }
+ append_rx_byte(sd, c);
+ }
+ return;
+ }
+ if (sd->fd < 0) {
+ return;
+ }
+ while (sd->rxbuf_len < (int)sizeof(sd->rxbuf)) {
+ uae_u8 c;
+ ssize_t got = read(sd->fd, &c, 1);
+ if (got == 1) {
+ append_rx_byte(sd, c);
+ continue;
+ }
+ return;
+ }
+}
+
+static int uaeser_pending(struct uaeserialdataunix *sd)
+{
+ if (sd->lock_valid) {
+ pthread_mutex_lock(&sd->lock);
+ }
+ poll_host_bytes(sd);
+ int pending = sd->rxbuf_len;
+ if (sd->lock_valid) {
+ pthread_mutex_unlock(&sd->lock);
+ }
+ return pending;
+}
+
+static void uaeser_thread(void *arg)
+{
+ struct uaeserialdataunix *sd = (struct uaeserialdataunix*)arg;
+ uae_set_thread_priority(NULL, 1);
+ sd->threadactive = 1;
+ uae_sem_post(&sd->sync_sem);
+ while (sd->threadactive == 1) {
+ int sigmask = 2;
+ if (uaeser_pending(sd) > 0) {
+ sigmask |= 1;
+ }
+ uaeser_signal(sd->user, sigmask);
+ Sleep(10);
+ }
+ sd->threadactive = 0;
+}
+
+int uaeser_getdatalength(void)
+{
+ return sizeof(struct uaeserialdataunix);
+}
+
+int uaeser_open(void *vsd, void *user, int unit)
+{
+ struct uaeserialdataunix *sd = (struct uaeserialdataunix*)vsd;
+ const TCHAR *sername = unix_uaeserial_get_port(unit);
+ memset(sd, 0, sizeof(*sd));
+ sd->fd = -1;
+ sd->listen_fd = -1;
+ sd->conn_fd = -1;
+ sd->user = user;
+ sd->unit = unit;
+ pthread_mutex_init(&sd->lock, NULL);
+ sd->lock_valid = true;
+
+ if (!sername[0] && unit == 0) {
+ sername = currprefs.sername;
+ }
+ if (!sername[0] || !_tcsicmp(sername, _T("none"))) {
+ write_log(_T("UAESER: no Unix host port configured for uaeserial.device unit %d\n"), unit);
+ pthread_mutex_destroy(&sd->lock);
+ sd->lock_valid = false;
+ return 0;
+ }
+ if (!_tcsnicmp(sername, _T("TCP://"), 6)) {
+ if (!opentcp(sd, sername + 4)) {
+ uaeser_close(sd);
+ return 0;
+ }
+ } else if (!_tcsnicmp(sername, _T("TCP:"), 4)) {
+ if (!opentcp(sd, sername + 4)) {
+ uaeser_close(sd);
+ return 0;
+ }
+ } else {
+ sd->fd = open(sername, O_RDWR | O_NOCTTY | O_NONBLOCK);
+ if (sd->fd < 0) {
+ write_log(_T("UAESER: failed to open '%s': %s\n"), sername, strerror(errno));
+ uaeser_close(sd);
+ return 0;
+ }
+ if (configure_serial_fd(sd, 9600, 8, 1, currprefs.serial_hwctsrts, 0, 0)) {
+ uaeser_close(sd);
+ return 0;
+ }
+ write_log(_T("UAESER: using %s for uaeserial.device unit %d\n"), sername, unit);
+ }
+ uae_sem_init(&sd->sync_sem, 0, 0);
+ uae_start_thread(_T("uaeserial_unix"), uaeser_thread, sd, NULL);
+ uae_sem_wait(&sd->sync_sem);
+ return 1;
+}
+
+void uaeser_close(void *vsd)
+{
+ struct uaeserialdataunix *sd = (struct uaeserialdataunix*)vsd;
+ if (sd->threadactive) {
+ sd->threadactive = -1;
+ while (sd->threadactive) {
+ Sleep(10);
+ }
+ }
+ if (sd->fd >= 0 && sd->saved_tios_valid) {
+ tcsetattr(sd->fd, TCSANOW, &sd->saved_tios);
+ }
+ if (sd->lock_valid) {
+ pthread_mutex_lock(&sd->lock);
+ }
+ close_fd(&sd->fd);
+ close_fd(&sd->conn_fd);
+ close_fd(&sd->listen_fd);
+ sd->tcpserial = false;
+ sd->saved_tios_valid = false;
+ sd->user = NULL;
+ if (sd->lock_valid) {
+ pthread_mutex_unlock(&sd->lock);
+ pthread_mutex_destroy(&sd->lock);
+ sd->lock_valid = false;
+ }
+}
+
+int uaeser_query(void *vsd, uae_u16 *status, uae_u32 *pending)
+{
+ struct uaeserialdataunix *sd = (struct uaeserialdataunix*)vsd;
+ if (pending) {
+ *pending = uaeser_pending(sd);
+ }
+ if (status) {
+ int modem = 0;
+ uae_u16 s = 0;
+ if (sd->tcpserial) {
+ if (tcp_accept_pending(sd)) {
+ modem = TIOCM_CTS | TIOCM_DSR | TIOCM_CAR;
+ }
+ } else if (sd->fd >= 0) {
+ ioctl(sd->fd, TIOCMGET, &modem);
+ }
+ s |= (modem & TIOCM_CTS) ? 0 : (1 << 4);
+ s |= (modem & TIOCM_DSR) ? 0 : (1 << 7);
+ s |= (modem & TIOCM_RI) ? (1 << 2) : 0;
+ *status = s;
+ }
+ return 1;
+}
+
+int uaeser_read(void *vsd, uae_u8 *data, uae_u32 len)
+{
+ struct uaeserialdataunix *sd = (struct uaeserialdataunix*)vsd;
+ if (len == 0) {
+ return 1;
+ }
+ if (sd->lock_valid) {
+ pthread_mutex_lock(&sd->lock);
+ }
+ poll_host_bytes(sd);
+ if (sd->rxbuf_len < (int)len) {
+ if (sd->lock_valid) {
+ pthread_mutex_unlock(&sd->lock);
+ }
+ return 0;
+ }
+ memcpy(data, sd->rxbuf, len);
+ sd->rxbuf_len -= len;
+ if (sd->rxbuf_len > 0) {
+ memmove(sd->rxbuf, sd->rxbuf + len, sd->rxbuf_len);
+ }
+ if (sd->lock_valid) {
+ pthread_mutex_unlock(&sd->lock);
+ }
+ return 1;
+}
+
+int uaeser_write(void *vsd, uae_u8 *data, uae_u32 len)
+{
+ struct uaeserialdataunix *sd = (struct uaeserialdataunix*)vsd;
+ if (len == 0) {
+ return 1;
+ }
+ if (sd->lock_valid) {
+ pthread_mutex_lock(&sd->lock);
+ }
+ int fd = sd->fd;
+ if (sd->tcpserial) {
+ if (!tcp_accept_pending(sd)) {
+ if (sd->lock_valid) {
+ pthread_mutex_unlock(&sd->lock);
+ }
+ return 0;
+ }
+ fd = sd->conn_fd;
+ }
+ if (fd < 0) {
+ if (sd->lock_valid) {
+ pthread_mutex_unlock(&sd->lock);
+ }
+ return 0;
+ }
+ uae_u32 done = 0;
+ while (done < len) {
+ ssize_t written = sd->tcpserial
+ ? send(fd, data + done, len - done, 0)
+ : write(fd, data + done, len - done);
+ if (written > 0) {
+ done += (uae_u32)written;
+ continue;
+ }
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ fd_set wfds;
+ FD_ZERO(&wfds);
+ FD_SET(fd, &wfds);
+ struct timeval tv = { 0, 10000 };
+ if (select(fd + 1, NULL, &wfds, NULL, &tv) > 0) {
+ continue;
+ }
+ }
+ if (sd->tcpserial) {
+ tcp_disconnect(sd);
+ }
+ if (sd->lock_valid) {
+ pthread_mutex_unlock(&sd->lock);
+ }
+ return done > 0 ? 1 : 0;
+ }
+ if (sd->lock_valid) {
+ pthread_mutex_unlock(&sd->lock);
+ }
+ return 1;
+}
+
+int uaeser_setparams(void *vsd, int baud, int, int bits, int sbits, int rtscts, int parity, uae_u32 xonxoff)
+{
+ struct uaeserialdataunix *sd = (struct uaeserialdataunix*)vsd;
+ if (sd->lock_valid) {
+ pthread_mutex_lock(&sd->lock);
+ }
+ int ret = configure_serial_fd(sd, baud, bits, sbits, rtscts, parity, xonxoff);
+ if (sd->lock_valid) {
+ pthread_mutex_unlock(&sd->lock);
+ }
+ return ret;
+}
+
+int uaeser_break(void *vsd, int)
+{
+ struct uaeserialdataunix *sd = (struct uaeserialdataunix*)vsd;
+ if (sd->lock_valid) {
+ pthread_mutex_lock(&sd->lock);
+ }
+ if (sd->fd >= 0) {
+ tcsendbreak(sd->fd, 0);
+ }
+ if (sd->lock_valid) {
+ pthread_mutex_unlock(&sd->lock);
+ }
+ return 1;
+}
+
+void uaeser_trigger(void *)
+{
+}
+
+void uaeser_clearbuffers(void *vsd)
+{
+ struct uaeserialdataunix *sd = (struct uaeserialdataunix*)vsd;
+ if (sd->lock_valid) {
+ pthread_mutex_lock(&sd->lock);
+ }
+ if (sd->fd >= 0) {
+ tcflush(sd->fd, TCIOFLUSH);
+ }
+ sd->rxbuf_len = 0;
+ if (sd->lock_valid) {
+ pthread_mutex_unlock(&sd->lock);
+ }
+}
+
+#endif
--- /dev/null
+#ifndef WINUAE_OD_UNIX_UAESERIAL_UNIX_H
+#define WINUAE_OD_UNIX_UAESERIAL_UNIX_H
+
+#define UNIX_UAESERIAL_MAX_UNITS 32
+
+const TCHAR *unix_uaeserial_get_port(int unit);
+void unix_uaeserial_set_port(int unit, const TCHAR *port);
+
+#endif /* WINUAE_OD_UNIX_UAESERIAL_UNIX_H */