From: Stefan Reinauer Date: Thu, 4 Jun 2026 14:58:53 +0000 (-0700) Subject: od-unix: add host device backends X-Git-Url: https://git.unchartedbackwaters.co.uk/w/?a=commitdiff_plain;h=6927ff35a2718c85750703c82ff0e5dd8d1b922a;p=francis%2Fwinuae.git od-unix: add host device backends Add Unix implementations for clipboard, bsdsocket, serial, uaeserial, parallel, MIDI, networking, ROM scanning, and board helper glue. These files back the target hooks and device interfaces used by the shared emulator code. --- diff --git a/od-unix/bsdsock_unix.cpp b/od-unix/bsdsock_unix.cpp new file mode 100644 index 00000000..b5d53757 --- /dev/null +++ b/od-unix/bsdsock_unix.cpp @@ -0,0 +1,2643 @@ +/* + * UAE - The Un*x Amiga Emulator + * + * bsdsocket.library emulation - Unix + * + * Copyright 2000-2001 Carl Drougge + * 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 + +#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 +#include +#include +#include +#ifdef HAVE_SYS_FILIO_H +# include +#endif +#include +#include +#include +#include +#include +#include +#include + +#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 +#endif +#endif /* __HAIKU__ */ + +#include +#include +#include +#include +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 running; // Thread running flag + std::vector 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(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(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 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 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 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 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 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; +} diff --git a/od-unix/clipboard.cpp b/od-unix/clipboard.cpp new file mode 100644 index 00000000..502930d7 --- /dev/null +++ b/od-unix/clipboard.cpp @@ -0,0 +1,1437 @@ +#include "sysconfig.h" +#include "sysdeps.h" + +#include +#include +#include +#include +#include + +#ifdef WINUAE_UNIX_WITH_IMAGEIO +#include +#include +#include +#define UAE_UNIX_CLIPBOARD_IMAGEIO 1 +#endif + +#ifdef UAE_UNIX_WITH_SDL3 +#include +#if SDL_VERSION_ATLEAST(3, 2, 0) +#define UAE_UNIX_WITH_SDL3_CLIPBOARD_DATA 1 +#endif +#endif + +#ifdef WINUAE_UNIX_WITH_LIBPNG +#include +#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 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 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 bmp; + std::vector png; + std::vector tiff; +}; + +static const void *SDLCALL unix_clipboard_data_callback(void *userdata, const char *mime_type, size_t *size) +{ + UnixClipboardData *data = static_cast(userdata); + if (!data) { + if (size) { + *size = 0; + } + return NULL; + } + const std::vector *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(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 *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 *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 *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 *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 &bmp, const std::vector *png, + const std::vector *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 &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 *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 *out, uae_u16 value) +{ + out->push_back(value >> 8); + out->push_back(value); +} + +static void append_le16(std::vector *out, uae_u16 value) +{ + out->push_back(value); + out->push_back(value >> 8); +} + +static void append_le32(std::vector *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 *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 pixels; +}; + +static bool parse_bmp(const std::vector &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 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 *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 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 &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 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 *png) +{ + if (!png || bitmap.width <= 0 || bitmap.height <= 0 || + bitmap.pixels.size() < (size_t)bitmap.width * (size_t)bitmap.height) { + return false; + } + + std::vector 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 &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 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 *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 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 make_iff_text(const std::string &host_text) +{ + const std::string amiga_text = host_text_to_amiga_text(host_text); + std::vector 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 *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 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 *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(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 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 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 bmp; + if (parse_iff_ilbm(buffer.data(), len, &bitmap) && make_bmp(bitmap, &bmp)) { + std::vector png; +#ifdef UAE_UNIX_CLIPBOARD_PNG + make_png(bitmap, &png); +#endif + std::vector 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 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); +} diff --git a/od-unix/dlopen.cpp b/od-unix/dlopen.cpp new file mode 100644 index 00000000..307e93d0 --- /dev/null +++ b/od-unix/dlopen.cpp @@ -0,0 +1,174 @@ +#include "sysconfig.h" +#include "sysdeps.h" + +#include "uae/dlopen.h" +#include "uae/log.h" +#include "target_dlopen.h" + +#include +#if defined(UAE_HOST_DARWIN) +#include +#elif defined(UAE_HOST_LINUX) +#include +#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; +} diff --git a/od-unix/midi.cpp b/od-unix/midi.cpp new file mode 100644 index 00000000..ca5f4898 --- /dev/null +++ b/od-unix/midi.cpp @@ -0,0 +1,766 @@ +#include "sysconfig.h" +#include "sysdeps.h" + +#ifdef WITH_MIDI + +#include +#include +#include + +#if defined(WINUAE_UNIX_WITH_COREMIDI) +#include +#include +#elif defined(WINUAE_UNIX_WITH_ALSA_MIDI) +#include +#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 midi_outputs; +static std::vector midi_inputs; +static bool midi_outputs_enumerated; +static bool midi_inputs_enumerated; +static MidiOutStatus out_status; +static std::vector sysex_buffer; +static std::deque 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 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 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 &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 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 lock(input_queue_mutex); + return input_queue.empty() ? 0 : 1; +} + +LONG getmidibyte(void) +{ + poll_alsa_midi_input(); + std::lock_guard lock(input_queue_mutex); + if (input_queue.empty()) { + return -1; + } + LONG value = input_queue.front(); + input_queue.pop_front(); + return value; +} + +#endif diff --git a/od-unix/midi.h b/od-unix/midi.h new file mode 100644 index 00000000..f2007442 --- /dev/null +++ b/od-unix/midi.h @@ -0,0 +1,73 @@ +/* + * 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 diff --git a/od-unix/parallel.cpp b/od-unix/parallel.cpp new file mode 100644 index 00000000..d065f074 --- /dev/null +++ b/od-unix/parallel.cpp @@ -0,0 +1,300 @@ +#include "sysconfig.h" +#include "sysdeps.h" + +#include "options.h" +#include "parallel.h" +#include "cia.h" + +#if defined(__linux__) +#include +#include +#include +#include +#include +#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 +} diff --git a/od-unix/pcem_wincompat.cpp b/od-unix/pcem_wincompat.cpp new file mode 100644 index 00000000..945c8431 --- /dev/null +++ b/od-unix/pcem_wincompat.cpp @@ -0,0 +1,252 @@ +#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 + +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; diff --git a/od-unix/pcem_wincompat.h b/od-unix/pcem_wincompat.h new file mode 100644 index 00000000..309b8d2b --- /dev/null +++ b/od-unix/pcem_wincompat.h @@ -0,0 +1,36 @@ +#ifndef WINUAE_OD_UNIX_PCEM_WINCOMPAT_H +#define WINUAE_OD_UNIX_PCEM_WINCOMPAT_H + +#include +#include + +#ifdef __cplusplus +#include +#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 diff --git a/od-unix/romscan.cpp b/od-unix/romscan.cpp new file mode 100644 index 00000000..930ee7ec --- /dev/null +++ b/od-unix/romscan.cpp @@ -0,0 +1,299 @@ +#include "sysconfig.h" +#include "sysdeps.h" + +#include + +#include +#include +#include + +#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; + 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(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 *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 rom_scan_paths(struct uae_prefs *prefs) +{ + std::vector 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; +} diff --git a/od-unix/romscan.h b/od-unix/romscan.h new file mode 100644 index 00000000..058d6463 --- /dev/null +++ b/od-unix/romscan.h @@ -0,0 +1,7 @@ +#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); diff --git a/od-unix/serial.cpp b/od-unix/serial.cpp new file mode 100644 index 00000000..9fc74c5e --- /dev/null +++ b/od-unix/serial.cpp @@ -0,0 +1,791 @@ +#include "sysconfig.h" +#include "sysdeps.h" + +#ifdef SERIAL_PORT + +#include +#include +#include +#include +#include + +#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 diff --git a/od-unix/sndboard_host.cpp b/od-unix/sndboard_host.cpp new file mode 100644 index 00000000..116de3d0 --- /dev/null +++ b/od-unix/sndboard_host.cpp @@ -0,0 +1,123 @@ +#include "sysconfig.h" +#include "sysdeps.h" + +#include "sndboard_host.h" +#include "uae.h" + +#ifdef UAE_UNIX_WITH_SDL3 + +#define SDL_MAIN_HANDLED +#include +#include + +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 diff --git a/od-unix/sndboard_host.h b/od-unix/sndboard_host.h new file mode 100644 index 00000000..ab599f41 --- /dev/null +++ b/od-unix/sndboard_host.h @@ -0,0 +1,9 @@ +#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 */ diff --git a/od-unix/target_dlopen.h b/od-unix/target_dlopen.h new file mode 100644 index 00000000..845cd972 --- /dev/null +++ b/od-unix/target_dlopen.h @@ -0,0 +1,4 @@ +#pragma once + +bool target_dlopen_plugin(const TCHAR *name, TCHAR *loaded_path, + size_t loaded_path_size, UAE_DLHANDLE *handlep); diff --git a/od-unix/target_main.h b/od-unix/target_main.h new file mode 100644 index 00000000..f0abc5a5 --- /dev/null +++ b/od-unix/target_main.h @@ -0,0 +1,4 @@ +#pragma once + +void target_main_set_args(int argc, TCHAR **argv); +int target_main_handle_early(int argc, TCHAR **argv); diff --git a/od-unix/uaenet_unix.cpp b/od-unix/uaenet_unix.cpp new file mode 100644 index 00000000..5e445368 --- /dev/null +++ b/od-unix/uaenet_unix.cpp @@ -0,0 +1,853 @@ +/* + * UAE - The Un*x Amiga Emulator + * + * Unix uaenet packet backend + * + * Copyright 2026 WinUAE contributors + */ + +#include "sysconfig.h" +#include "sysdeps.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) +#include +#include +#endif +#if defined(__linux__) +#include +#include +#include +#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)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; +} diff --git a/od-unix/uaeserial_host.cpp b/od-unix/uaeserial_host.cpp new file mode 100644 index 00000000..8193fa4d --- /dev/null +++ b/od-unix/uaeserial_host.cpp @@ -0,0 +1,581 @@ +#include "sysconfig.h" +#include "sysdeps.h" + +#ifdef UAESERIAL + +#include +#include +#include +#include +#include + +#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 diff --git a/od-unix/uaeserial_unix.h b/od-unix/uaeserial_unix.h new file mode 100644 index 00000000..58ab0e3d --- /dev/null +++ b/od-unix/uaeserial_unix.h @@ -0,0 +1,9 @@ +#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 */