diff --git a/CMakeLists.txt b/CMakeLists.txt
index b3c00c8ae7..2b2f106805 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -350,6 +350,9 @@ if(EMSCRIPTEN)
# We use IDBFS for persistent storage.
target_link_libraries(WASM::WASM INTERFACE "-lidbfs.js")
+ # Socket proxy
+ target_link_libraries(WASM::WASM INTERFACE "-lwebsocket.js")
+
# Use custom pre-js and shell.html.
target_link_libraries(WASM::WASM INTERFACE "--pre-js ${CMAKE_SOURCE_DIR}/os/emscripten/pre.js")
target_link_libraries(WASM::WASM INTERFACE "--shell-file ${CMAKE_SOURCE_DIR}/os/emscripten/shell.html")
diff --git a/src/network/core/CMakeLists.txt b/src/network/core/CMakeLists.txt
index bf713be99c..ea91e81959 100644
--- a/src/network/core/CMakeLists.txt
+++ b/src/network/core/CMakeLists.txt
@@ -4,6 +4,8 @@ add_files(
config.h
core.cpp
core.h
+ em_proxy.cpp
+ em_proxy.h
game_info.cpp
game_info.h
host.cpp
diff --git a/src/network/core/core.cpp b/src/network/core/core.cpp
index 563deae963..f83260e51a 100644
--- a/src/network/core/core.cpp
+++ b/src/network/core/core.cpp
@@ -34,6 +34,9 @@ bool NetworkCoreInitialize()
}
}
#endif /* _WIN32 */
+#if defined(__EMSCRIPTEN__)
+ em_proxy_initialize_network();
+#endif
return true;
}
diff --git a/src/network/core/em_proxy.cpp b/src/network/core/em_proxy.cpp
new file mode 100644
index 0000000000..c676c6c8ed
--- /dev/null
+++ b/src/network/core/em_proxy.cpp
@@ -0,0 +1,933 @@
+#if defined(__EMSCRIPTEN__)
+
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD 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, version 2.
+ * OpenTTD 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 OpenTTD. If not, see .
+ */
+
+/**
+ * @file em_proxy.cpp Emscripten socket proxy, implementation copied from here:
+ * https://github.com/emscripten-core/emscripten/blob/main/system/lib/websocket/websocket_to_posix_socket.cpp
+ */
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#if defined(__APPLE__) || defined(__linux__)
+#include
+#endif
+
+#define EM_PROXY_NO_MACROS 1
+
+#include "../../stdafx.h"
+#include "../../debug.h"
+#include "os_abstraction.h"
+#include "em_proxy.h"
+
+
+// Uncomment to enable debug printing
+// #define POSIX_SOCKET_DEBUG
+
+// Uncomment to enable more verbose debug printing (in addition to uncommenting POSIX_SOCKET_DEBUG)
+// #define POSIX_SOCKET_DEEP_DEBUG
+
+#define MIN(a,b) (((a)<(b))?(a):(b))
+
+static void *memdup(const void *ptr, size_t sz)
+{
+ if (!ptr) return 0;
+ void *dup = malloc(sz);
+ if (dup) memcpy(dup, ptr, sz);
+ return dup;
+}
+
+// Each proxied socket call has at least the following data.
+struct SocketCallHeader
+{
+ int callId;
+ int function;
+};
+
+// Each socket call returns at least the following data.
+struct SocketCallResultHeader
+{
+ int callId;
+ int ret;
+ int errno_;
+ // Buffer can contain more data here, conceptually:
+ // uint8_t extraData[];
+};
+
+struct PosixSocketCallResult
+{
+ PosixSocketCallResult *next;
+ int callId;
+ int operationCompleted;
+
+ // Before the call has finished, this field represents the minimum expected number of bytes that server will need to report back.
+ // After the call has finished, this field reports back the number of bytes pointed to by data, >= the expected value.
+ int bytes;
+
+ // Result data:
+ SocketCallResultHeader *data;
+};
+
+// Shield multithreaded accesses to POSIX sockets functions in the program, namely the two variables 'bridgeSocket' and 'callResultHead' below.
+static pthread_mutex_t bridgeLock = PTHREAD_MUTEX_INITIALIZER;
+
+// Socket handle for the connection from browser WebSocket to the sockets bridge proxy server.
+static EMSCRIPTEN_WEBSOCKET_T bridgeSocket = (EMSCRIPTEN_WEBSOCKET_T)0;
+
+// https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/readyState
+enum { BRIDGE_CONNECTING = 0, BRIDGE_OPEN = 1, BRIDGE_CLOSING = 2, BRIDGE_CLOSED = 3 };
+static uint16_t bridgeState = BRIDGE_CONNECTING;
+
+// Stores a linked list of all currently pending sockets operations (ones that are waiting for a reply back from the sockets proxy server)
+static PosixSocketCallResult *callResultHead = 0;
+
+static PosixSocketCallResult *allocate_call_result(int expectedBytes)
+{
+ pthread_mutex_lock(&bridgeLock); // Guard multithreaded access to 'callResultHead' and 'nextId' below
+ PosixSocketCallResult *b = (PosixSocketCallResult*)(malloc(sizeof(PosixSocketCallResult)));
+ if (!b)
+ {
+#ifdef POSIX_SOCKET_DEBUG
+ emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "allocate_call_result: Failed to allocate call result struct of size %d bytes!\n", (int)sizeof(PosixSocketCallResult));
+#endif
+ pthread_mutex_unlock(&bridgeLock);
+ return 0;
+ }
+ static int nextId = 1;
+ b->callId = nextId++;
+#ifdef POSIX_SOCKET_DEEP_DEBUG
+ emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "allocate_call_result: allocated call ID %d\n", b->callId);
+#endif
+ b->bytes = expectedBytes;
+ b->data = 0;
+ b->operationCompleted = 0;
+ b->next = 0;
+
+ if (!callResultHead)
+ callResultHead = b;
+ else
+ {
+ PosixSocketCallResult *t = callResultHead;
+ while(t->next) t = t->next;
+ t->next = b;
+ }
+ pthread_mutex_unlock(&bridgeLock);
+ return b;
+}
+
+static void free_call_result(PosixSocketCallResult *buffer)
+{
+#ifdef POSIX_SOCKET_DEEP_DEBUG
+ if (buffer)
+ emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "free_call_result: freed call ID %d\n", buffer->callId);
+#endif
+
+ if (buffer->data) free(buffer->data);
+ free(buffer);
+}
+
+PosixSocketCallResult *pop_call_result(int callId)
+{
+ pthread_mutex_lock(&bridgeLock); // Guard multithreaded access to 'callResultHead'
+ PosixSocketCallResult *prev = 0;
+ PosixSocketCallResult *b = callResultHead;
+ while(b)
+ {
+ if (b->callId == callId)
+ {
+ if (prev) prev->next = b->next;
+ else callResultHead = b->next;
+ b->next = 0;
+#ifdef POSIX_SOCKET_DEEP_DEBUG
+ emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "pop_call_result: Removed call ID %d from pending sockets call queue\n", callId);
+#endif
+ pthread_mutex_unlock(&bridgeLock);
+ return b;
+ }
+ prev = b;
+ b = b->next;
+ }
+ pthread_mutex_unlock(&bridgeLock);
+#ifdef POSIX_SOCKET_DEBUG
+ emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "pop_call_result: No such call ID %d in pending sockets call queue!\n", callId);
+#endif
+ return 0;
+}
+
+void wait_for_call_result(PosixSocketCallResult *b)
+{
+#ifdef POSIX_SOCKET_DEEP_DEBUG
+ emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "wait_for_call_result: Waiting for call ID %d\n", b->callId);
+#endif
+ while(!emscripten_atomic_load_u32(&b->operationCompleted))
+ emscripten_futex_wait(&b->operationCompleted, 0, 1e9);
+#ifdef POSIX_SOCKET_DEEP_DEBUG
+ emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "wait_for_call_result: Waiting for call ID %d done\n", b->callId);
+#endif
+}
+
+static EM_BOOL bridge_socket_on_message(int eventType, const EmscriptenWebSocketMessageEvent *websocketEvent, void *userData)
+{
+ if (websocketEvent->numBytes < sizeof(SocketCallResultHeader))
+ {
+ emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "Received corrupt WebSocket result message with size %d, not enough space for header, at least %d bytes!\n", (int)websocketEvent->numBytes, (int)sizeof(SocketCallResultHeader));
+ return EM_TRUE;
+ }
+
+ SocketCallResultHeader *header = (SocketCallResultHeader *)websocketEvent->data;
+
+#ifdef POSIX_SOCKET_DEEP_DEBUG
+ emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "POSIX sockets bridge received message on thread %p, size: %d bytes, for call ID %d\n", (void*)pthread_self(), websocketEvent->numBytes, header->callId);
+#endif
+
+ PosixSocketCallResult *b = pop_call_result(header->callId);
+ if (!b)
+ {
+ emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "Received WebSocket result message to unknown call ID %d!\n", (int)header->callId);
+ // TODO: Craft a socket result that signifies a failure, and wake the listening thread
+ return EM_TRUE;
+ }
+
+ if ((int)websocketEvent->numBytes < b->bytes)
+ {
+ emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "Received corrupt WebSocket result message with size %d, expected at least %d bytes!\n", (int)websocketEvent->numBytes, b->bytes);
+ // TODO: Craft a socket result that signifies a failure, and wake the listening thread
+ return EM_TRUE;
+ }
+
+ b->bytes = websocketEvent->numBytes;
+ b->data = (SocketCallResultHeader*)memdup(websocketEvent->data, websocketEvent->numBytes);
+
+ if (!b->data)
+ {
+ emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "Out of memory, tried to allocate %d bytes!\n", websocketEvent->numBytes);
+ return EM_TRUE;
+ }
+
+ if (b->operationCompleted != 0)
+ {
+ emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "Memory corruption(?): the received result for completed operation at address %p was expected to be in state 0, but it was at state %d!\n", &b->operationCompleted, (int)b->operationCompleted);
+ }
+
+ emscripten_atomic_store_u32(&b->operationCompleted, 1);
+ emscripten_futex_wake(&b->operationCompleted, 0x7FFFFFFF);
+
+ return EM_TRUE;
+}
+
+EMSCRIPTEN_WEBSOCKET_T emscripten_init_websocket_to_posix_socket_bridge(const char *bridgeUrl)
+{
+#ifdef POSIX_SOCKET_DEBUG
+ emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_JS_STACK, "emscripten_init_websocket_to_posix_socket_bridge(bridgeUrl=\"%s\")\n", bridgeUrl);
+#endif
+ pthread_mutex_lock(&bridgeLock); // Guard multithreaded access to 'bridgeSocket'
+ if (bridgeSocket)
+ {
+#ifdef POSIX_SOCKET_DEBUG
+ emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_WARN | EM_LOG_JS_STACK, "emscripten_init_websocket_to_posix_socket_bridge(bridgeUrl=\"%s\"): A previous bridge socket connection handle existed! Forcibly tearing old connection down.\n", bridgeUrl);
+#endif
+ emscripten_websocket_close(bridgeSocket, 0, 0);
+ emscripten_websocket_delete(bridgeSocket);
+ bridgeSocket = 0;
+ }
+ EmscriptenWebSocketCreateAttributes attr;
+ emscripten_websocket_init_create_attributes(&attr);
+ attr.url = bridgeUrl;
+ bridgeSocket = emscripten_websocket_new(&attr);
+ emscripten_websocket_set_onmessage_callback_on_thread(bridgeSocket, 0, bridge_socket_on_message, EM_CALLBACK_THREAD_CONTEXT_MAIN_BROWSER_THREAD);
+
+ pthread_mutex_unlock(&bridgeLock);
+ return bridgeSocket;
+}
+
+int em_proxy_initialize_network(void)
+{
+ const char * proxy = "ws://localhost:3999";
+ int bridge = emscripten_init_websocket_to_posix_socket_bridge(proxy);
+ DEBUG(net, 0, "[em_proxy] Opened proxy socket to %s: socket %d", proxy, bridge);
+ return (bridge > 0);
+}
+
+#define POSIX_SOCKET_MSG_SOCKET 1
+#define POSIX_SOCKET_MSG_SOCKETPAIR 2
+#define POSIX_SOCKET_MSG_SHUTDOWN 3
+#define POSIX_SOCKET_MSG_BIND 4
+#define POSIX_SOCKET_MSG_CONNECT 5
+#define POSIX_SOCKET_MSG_LISTEN 6
+#define POSIX_SOCKET_MSG_ACCEPT 7
+#define POSIX_SOCKET_MSG_GETSOCKNAME 8
+#define POSIX_SOCKET_MSG_GETPEERNAME 9
+#define POSIX_SOCKET_MSG_SEND 10
+#define POSIX_SOCKET_MSG_RECV 11
+#define POSIX_SOCKET_MSG_SENDTO 12
+#define POSIX_SOCKET_MSG_RECVFROM 13
+#define POSIX_SOCKET_MSG_SENDMSG 14
+#define POSIX_SOCKET_MSG_RECVMSG 15
+#define POSIX_SOCKET_MSG_GETSOCKOPT 16
+#define POSIX_SOCKET_MSG_SETSOCKOPT 17
+#define POSIX_SOCKET_MSG_GETADDRINFO 18
+#define POSIX_SOCKET_MSG_GETNAMEINFO 19
+
+#define MAX_SOCKADDR_SIZE 256
+#define MAX_OPTIONVALUE_SIZE 16
+
+int em_proxy_socket(int domain, int type, int protocol)
+{
+#ifdef POSIX_SOCKET_DEBUG
+ emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "socket(domain=%d,type=%d,protocol=%d) on thread %p\n", domain, type, protocol, (void*)pthread_self());
+#endif
+
+ if (bridgeState == BRIDGE_CONNECTING) {
+ if (bridgeSocket > 0) {
+ emscripten_websocket_get_ready_state(bridgeSocket, &bridgeState);
+ }
+ DEBUG(net, 0, "[em_proxy] Proxy socket state %d: %s", bridgeState, bridgeState == BRIDGE_OPEN ? "connected" : "proxy disabled");
+ if (bridgeState == BRIDGE_CONNECTING) {
+ bridgeState = BRIDGE_CLOSED;
+ }
+ }
+ if (bridgeState != BRIDGE_OPEN) {
+ return socket(domain, type, protocol);
+ }
+
+ struct {
+ SocketCallHeader header;
+ int domain;
+ int type;
+ int protocol;
+ } d;
+
+ PosixSocketCallResult *b = allocate_call_result(sizeof(SocketCallResultHeader));
+ d.header.callId = b->callId;
+ d.header.function = POSIX_SOCKET_MSG_SOCKET;
+ d.domain = domain;
+ d.type = type;
+ d.protocol = protocol;
+ emscripten_websocket_send_binary(bridgeSocket, &d, sizeof(d));
+
+ wait_for_call_result(b);
+ int ret = b->data->ret;
+ if (ret < 0) errno = b->data->errno_;
+ free_call_result(b);
+ return ret;
+}
+
+int em_proxy_closesocket(int socket)
+{
+ if (bridgeState != BRIDGE_OPEN) {
+ return closesocket(socket);
+ }
+
+ // TODO: closing UDP socket not implemented
+ return em_proxy_shutdown(socket, SHUT_RDWR);
+}
+
+int em_proxy_shutdown(int socket, int how)
+{
+#ifdef POSIX_SOCKET_DEBUG
+ emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "shutdown(socket=%d,how=%d)\n", socket, how);
+#endif
+
+ if (bridgeState != BRIDGE_OPEN) {
+ return shutdown(socket, how);
+ }
+
+ struct {
+ SocketCallHeader header;
+ int socket;
+ int how;
+ } d;
+
+ PosixSocketCallResult *b = allocate_call_result(sizeof(SocketCallResultHeader));
+ d.header.callId = b->callId;
+ d.header.function = POSIX_SOCKET_MSG_SHUTDOWN;
+ d.socket = socket;
+ d.how = how;
+ emscripten_websocket_send_binary(bridgeSocket, &d, sizeof(d));
+
+ wait_for_call_result(b);
+ int ret = b->data->ret;
+ if (ret != 0) errno = b->data->errno_;
+ free_call_result(b);
+ return ret;
+}
+
+int em_proxy_bind(int socket, const struct sockaddr *address, socklen_t address_len)
+{
+#ifdef POSIX_SOCKET_DEBUG
+ emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "bind(socket=%d,address=%p,address_len=%d)\n", socket, address, address_len);
+#endif
+
+ if (bridgeState != BRIDGE_OPEN) {
+ return bind(socket, address, address_len);
+ }
+
+ struct Data {
+ SocketCallHeader header;
+ int socket;
+ uint32_t/*socklen_t*/ address_len;
+ uint8_t address[];
+ };
+ int numBytes = sizeof(Data) + address_len;
+ Data *d = (Data*)malloc(numBytes);
+
+ PosixSocketCallResult *b = allocate_call_result(sizeof(SocketCallResultHeader));
+ d->header.callId = b->callId;
+ d->header.function = POSIX_SOCKET_MSG_BIND;
+ d->socket = socket;
+ d->address_len = address_len;
+ if (address) memcpy(d->address, address, address_len);
+ else memset(d->address, 0, address_len);
+ emscripten_websocket_send_binary(bridgeSocket, d, numBytes);
+
+ wait_for_call_result(b);
+ int ret = b->data->ret;
+ if (ret != 0) errno = b->data->errno_;
+ free_call_result(b);
+
+ free(d);
+ return ret;
+}
+
+int em_proxy_connect(int socket, const struct sockaddr *address, socklen_t address_len)
+{
+#ifdef POSIX_SOCKET_DEBUG
+ emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "connect(socket=%d,address=%p,address_len=%d)\n", socket, address, address_len);
+#endif
+
+ if (bridgeState != BRIDGE_OPEN) {
+ return connect(socket, address, address_len);
+ }
+
+ struct Data {
+ SocketCallHeader header;
+ int socket;
+ uint32_t/*socklen_t*/ address_len;
+ uint8_t address[];
+ };
+ int numBytes = sizeof(Data) + address_len;
+ Data *d = (Data*)malloc(numBytes);
+
+ PosixSocketCallResult *b = allocate_call_result(sizeof(SocketCallResultHeader));
+ d->header.callId = b->callId;
+ d->header.function = POSIX_SOCKET_MSG_CONNECT;
+ d->socket = socket;
+ d->address_len = address_len;
+ if (address) memcpy(d->address, address, address_len);
+ else memset(d->address, 0, address_len);
+ emscripten_websocket_send_binary(bridgeSocket, d, numBytes);
+
+ wait_for_call_result(b);
+ int ret = b->data->ret;
+ if (ret != 0) errno = b->data->errno_;
+ free_call_result(b);
+
+ free(d);
+ return ret;
+}
+
+int em_proxy_listen(int socket, int backlog)
+{
+#ifdef POSIX_SOCKET_DEBUG
+ emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "listen(socket=%d,backlog=%d)\n", socket, backlog);
+#endif
+
+ if (bridgeState != BRIDGE_OPEN) {
+ return listen(socket, backlog);
+ }
+
+ struct {
+ SocketCallHeader header;
+ int socket;
+ int backlog;
+ } d;
+
+ PosixSocketCallResult *b = allocate_call_result(sizeof(SocketCallResultHeader));
+ d.header.callId = b->callId;
+ d.header.function = POSIX_SOCKET_MSG_LISTEN;
+ d.socket = socket;
+ d.backlog = backlog;
+ emscripten_websocket_send_binary(bridgeSocket, &d, sizeof(d));
+
+ wait_for_call_result(b);
+ int ret = b->data->ret;
+ if (ret != 0) errno = b->data->errno_;
+ free_call_result(b);
+ return ret;
+}
+
+int em_proxy_accept(int socket, struct sockaddr *address, socklen_t *address_len)
+{
+#ifdef POSIX_SOCKET_DEBUG
+ emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "accept(socket=%d,address=%p,address_len=%p)\n", socket, address, address_len);
+#endif
+
+ if (bridgeState != BRIDGE_OPEN) {
+ return accept(socket, address, address_len);
+ }
+
+ struct {
+ SocketCallHeader header;
+ int socket;
+ uint32_t/*socklen_t*/ address_len;
+ } d;
+
+ PosixSocketCallResult *b = allocate_call_result(sizeof(SocketCallResultHeader));
+ d.header.callId = b->callId;
+ d.header.function = POSIX_SOCKET_MSG_ACCEPT;
+ d.socket = socket;
+ d.address_len = address_len ? *address_len : 0;
+ emscripten_websocket_send_binary(bridgeSocket, &d, sizeof(d));
+
+ struct Result {
+ SocketCallResultHeader header;
+ int address_len;
+ uint8_t address[];
+ };
+
+ wait_for_call_result(b);
+ int ret = b->data->ret;
+ if (ret == 0)
+ {
+ Result *r = (Result*)b->data;
+ int realAddressLen = MIN(b->bytes - (int)sizeof(Result), r->address_len);
+ int copiedAddressLen = MIN((int)*address_len, realAddressLen);
+ if (address) memcpy(address, r->address, copiedAddressLen);
+ if (address_len) *address_len = realAddressLen;
+ }
+ else
+ {
+ errno = b->data->errno_;
+ }
+ free_call_result(b);
+ return ret;
+}
+
+ssize_t em_proxy_send(int socket, const void *message, size_t length, int flags)
+{
+#ifdef POSIX_SOCKET_DEBUG
+ emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "send(socket=%d,message=%p,length=%zd,flags=%d)\n", socket, message, length, flags);
+#endif
+
+ if (bridgeState != BRIDGE_OPEN) {
+ return send(socket, message, length, flags);
+ }
+
+ struct MSG {
+ SocketCallHeader header;
+ int socket;
+ uint32_t/*size_t*/ length;
+ int flags;
+ uint8_t message[];
+ };
+ size_t sz = sizeof(MSG)+length;
+ MSG *d = (MSG*)malloc(sz);
+
+ PosixSocketCallResult *b = allocate_call_result(sizeof(SocketCallResultHeader));
+ d->header.callId = b->callId;
+ d->header.function = POSIX_SOCKET_MSG_SEND;
+ d->socket = socket;
+ d->length = length;
+ d->flags = flags;
+ if (message) memcpy(d->message, message, length);
+ else memset(d->message, 0, length);
+ emscripten_websocket_send_binary(bridgeSocket, d, sz);
+
+ wait_for_call_result(b);
+ int ret = b->data->ret;
+ if (ret < 0) errno = b->data->errno_;
+ free_call_result(b);
+
+ free(d);
+ return ret;
+}
+
+ssize_t em_proxy_recv(int socket, void *buffer, size_t length, int flags)
+{
+#ifdef POSIX_SOCKET_DEBUG
+ emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "recv(socket=%d,buffer=%p,length=%zd,flags=%d)\n", socket, buffer, length, flags);
+#endif
+
+ if (bridgeState != BRIDGE_OPEN) {
+ return recv(socket, buffer, length, flags);
+ }
+
+ struct {
+ SocketCallHeader header;
+ int socket;
+ uint32_t/*size_t*/ length;
+ int flags;
+ } d;
+
+ PosixSocketCallResult *b = allocate_call_result(sizeof(SocketCallResultHeader));
+ d.header.callId = b->callId;
+ d.header.function = POSIX_SOCKET_MSG_RECV;
+ d.socket = socket;
+ d.length = length;
+ d.flags = flags;
+ emscripten_websocket_send_binary(bridgeSocket, &d, sizeof(d));
+
+ wait_for_call_result(b);
+ int ret = b->data->ret;
+ if (ret >= 0)
+ {
+ struct Result {
+ SocketCallResultHeader header;
+ uint8_t data[];
+ };
+ Result *r = (Result*)b->data;
+ if (buffer) memcpy(buffer, r->data, MIN(ret, (int)length));
+ }
+ else
+ {
+ errno = b->data->errno_;
+ }
+ free_call_result(b);
+
+ return ret;
+}
+
+ssize_t em_proxy_sendto(int socket, const void *message, size_t length, int flags, const struct sockaddr *dest_addr, socklen_t dest_len)
+{
+#ifdef POSIX_SOCKET_DEBUG
+ emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "sendto(socket=%d,message=%p,length=%zd,flags=%d,dest_addr=%p,dest_len=%d)\n", socket, message, length, flags, dest_addr, dest_len);
+#endif
+
+ if (bridgeState != BRIDGE_OPEN) {
+ return sendto(socket, message, length, flags, dest_addr, dest_len);
+ }
+
+ struct MSG {
+ SocketCallHeader header;
+ int socket;
+ uint32_t/*size_t*/ length;
+ int flags;
+ uint32_t/*socklen_t*/ dest_len;
+ uint8_t dest_addr[MAX_SOCKADDR_SIZE];
+ uint8_t message[];
+ };
+ size_t sz = sizeof(MSG)+length;
+ MSG *d = (MSG*)malloc(sz);
+
+ PosixSocketCallResult *b = allocate_call_result(sizeof(SocketCallResultHeader));
+ d->header.callId = b->callId;
+ d->header.function = POSIX_SOCKET_MSG_SENDTO;
+ d->socket = socket;
+ d->length = length;
+ d->flags = flags;
+ d->dest_len = dest_len;
+ memset(d->dest_addr, 0, sizeof(d->dest_addr));
+ if (dest_addr) memcpy(d->dest_addr, dest_addr, dest_len);
+ if (message) memcpy(d->message, message, length);
+ else memset(d->message, 0, length);
+ emscripten_websocket_send_binary(bridgeSocket, d, sz);
+
+ wait_for_call_result(b);
+ int ret = b->data->ret;
+ if (ret < 0) errno = b->data->errno_;
+ free_call_result(b);
+
+ free(d);
+ return ret;
+}
+
+ssize_t em_proxy_recvfrom(int socket, void *buffer, size_t length, int flags, struct sockaddr *address, socklen_t *address_len)
+{
+#ifdef POSIX_SOCKET_DEBUG
+ emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "recvfrom(socket=%d,buffer=%p,length=%zd,flags=%d,address=%p,address_len=%p)\n", socket, buffer, length, flags, address, address_len);
+#endif
+
+ if (bridgeState != BRIDGE_OPEN) {
+ return recvfrom(socket, buffer, length, flags, address, address_len);
+ }
+
+ struct {
+ SocketCallHeader header;
+ int socket;
+ uint32_t/*size_t*/ length;
+ int flags;
+ uint32_t/*socklen_t*/ address_len;
+ } d;
+
+ PosixSocketCallResult *b = allocate_call_result(sizeof(SocketCallResultHeader));
+ d.header.callId = b->callId;
+ d.header.function = POSIX_SOCKET_MSG_RECVFROM;
+ d.socket = socket;
+ d.length = length;
+ d.flags = flags;
+ d.address_len = *address_len;
+ emscripten_websocket_send_binary(bridgeSocket, &d, sizeof(d));
+
+ wait_for_call_result(b);
+ int ret = b->data->ret;
+ if (ret >= 0)
+ {
+ struct Result {
+ SocketCallResultHeader header;
+ int data_len;
+ int address_len; // N.B. this is the reported address length of the sender, that may be larger than what is actually serialized to this message.
+ uint8_t data_and_address[];
+ };
+ Result *r = (Result*)b->data;
+ if (buffer) memcpy(buffer, r->data_and_address, MIN(r->data_len, (int)length));
+ int copiedAddressLen = MIN((address_len ? (int)*address_len : 0), r->address_len);
+ if (address) memcpy(address, r->data_and_address + r->data_len, copiedAddressLen);
+ if (address_len) *address_len = r->address_len;
+ }
+ else
+ {
+ errno = b->data->errno_;
+ }
+ free_call_result(b);
+
+ return ret;
+}
+
+int em_proxy_getsockopt(int socket, int level, int option_name, void *option_value, socklen_t *option_len)
+{
+#ifdef POSIX_SOCKET_DEBUG
+ emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "getsockopt(socket=%d,level=%d,option_name=%d,option_value=%p,option_len=%p)\n", socket, level, option_name, option_value, option_len);
+#endif
+
+ if (bridgeState != BRIDGE_OPEN) {
+ return getsockopt(socket, level, option_name, option_value, option_len);
+ }
+
+ struct {
+ SocketCallHeader header;
+ int socket;
+ int level;
+ int option_name;
+ uint32_t/*socklen_t*/ option_len;
+ } d;
+
+ struct Result {
+ SocketCallResultHeader header;
+ uint8_t option_value[];
+ };
+
+ PosixSocketCallResult *b = allocate_call_result(sizeof(Result));
+ d.header.callId = b->callId;
+ d.header.function = POSIX_SOCKET_MSG_GETSOCKOPT;
+ d.socket = socket;
+ d.level = level;
+ d.option_name = option_name;
+ d.option_len = *option_len;
+ emscripten_websocket_send_binary(bridgeSocket, &d, sizeof(d));
+
+ wait_for_call_result(b);
+ int ret = b->data->ret;
+ if (ret == 0)
+ {
+ Result *r = (Result*)b->data;
+ int optLen = b->bytes - sizeof(Result);
+ if (option_value) memcpy(option_value, r->option_value, MIN((int)*option_len, optLen));
+ if (option_len) *option_len = optLen;
+ }
+ else
+ {
+ errno = b->data->errno_;
+ }
+ free_call_result(b);
+ return ret;
+}
+
+int em_proxy_setsockopt(int socket, int level, int option_name, const void *option_value, socklen_t option_len)
+{
+#ifdef POSIX_SOCKET_DEBUG
+ emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "setsockopt(socket=%d,level=%d,option_name=%d,option_value=%p,option_len=%d)\n", socket, level, option_name, option_value, option_len);
+#endif
+
+ if (bridgeState != BRIDGE_OPEN) {
+ return setsockopt(socket, level, option_name, option_value, option_len);
+ }
+
+ struct MSG {
+ SocketCallHeader header;
+ int socket;
+ int level;
+ int option_name;
+ int option_len;
+ uint8_t option_value[];
+ };
+ int messageSize = sizeof(MSG) + option_len;
+ MSG *d = (MSG*)malloc(messageSize);
+
+ PosixSocketCallResult *b = allocate_call_result(sizeof(SocketCallResultHeader));
+ d->header.callId = b->callId;
+ d->header.function = POSIX_SOCKET_MSG_SETSOCKOPT;
+ d->socket = socket;
+ d->level = level;
+ d->option_name = option_name;
+ if (option_value) memcpy(d->option_value, option_value, option_len);
+ else memset(d->option_value, 0, option_len);
+ d->option_len = option_len;
+ emscripten_websocket_send_binary(bridgeSocket, d, messageSize);
+
+ wait_for_call_result(b);
+ int ret = b->data->ret;
+ if (ret != 0) errno = b->data->errno_;
+ free_call_result(b);
+
+ free(d);
+ return ret;
+}
+
+// Host name resolution:
+
+int em_proxy_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res)
+{
+#define MAX_NODE_LEN 2048
+#define MAX_SERVICE_LEN 128
+
+ if (bridgeState != BRIDGE_OPEN) {
+ return getaddrinfo(node, service, hints, res);
+ }
+
+ struct {
+ SocketCallHeader header;
+ char node[MAX_NODE_LEN]; // Arbitrary max length limit
+ char service[MAX_SERVICE_LEN]; // Arbitrary max length limit
+ int hasHints;
+ int ai_flags;
+ int ai_family;
+ int ai_socktype;
+ int ai_protocol;
+ } d;
+
+ struct ResAddrinfo
+ {
+ int ai_flags;
+ int ai_family;
+ int ai_socktype;
+ int ai_protocol;
+ int/*socklen_t*/ ai_addrlen;
+ uint8_t /*sockaddr **/ ai_addr[];
+ };
+
+ struct Result {
+ SocketCallResultHeader header;
+ char ai_canonname[MAX_NODE_LEN];
+ int addrCount;
+ uint8_t /*ResAddrinfo[]*/ addr[];
+ };
+
+ memset(&d, 0, sizeof(d));
+ PosixSocketCallResult *b = allocate_call_result(sizeof(Result));
+ d.header.callId = b->callId;
+ d.header.function = POSIX_SOCKET_MSG_GETADDRINFO;
+ if (node)
+ {
+ assert(strlen(node) <= MAX_NODE_LEN-1);
+ strncpy(d.node, node, MAX_NODE_LEN-1);
+ }
+ if (service)
+ {
+ assert(strlen(service) <= MAX_SERVICE_LEN-1);
+ strncpy(d.service, service, MAX_SERVICE_LEN-1);
+ }
+ d.hasHints = !!hints;
+ if (hints)
+ {
+ d.ai_flags = hints->ai_flags;
+ d.ai_family = hints->ai_family;
+ d.ai_socktype = hints->ai_socktype;
+ d.ai_protocol = hints->ai_protocol;
+ }
+
+#ifdef POSIX_SOCKET_DEBUG
+ emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "getaddrinfo(node=%s,service=%s,hasHints=%d,ai_flags=%d,ai_family=%d,ai_socktype=%d,ai_protocol=%d,hintsPtr=%p,resPtr=%p)\n", node, service, d.hasHints, d.ai_flags, d.ai_family, d.ai_socktype, d.ai_protocol, hints, res);
+#endif
+
+ emscripten_websocket_send_binary(bridgeSocket, &d, sizeof(d));
+
+ wait_for_call_result(b);
+ int ret = b->data->ret;
+#ifdef POSIX_SOCKET_DEBUG
+ emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "getaddrinfo finished, ret=%d\n", ret);
+#endif
+ if (ret == 0)
+ {
+ if (res)
+ {
+ Result *r = (Result*)b->data;
+ uint8_t *raiAddr = (uint8_t*)&r->addr[0];
+ addrinfo *results = (addrinfo*)malloc(sizeof(addrinfo)*r->addrCount);
+#ifdef POSIX_SOCKET_DEBUG
+ emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "%d results\n", r->addrCount);
+#endif
+ for(size_t i = 0; (int)i < r->addrCount; ++i)
+ {
+ ResAddrinfo *rai = (ResAddrinfo*)raiAddr;
+ results[i].ai_flags = rai->ai_flags;
+ results[i].ai_family = rai->ai_family;
+ results[i].ai_socktype = rai->ai_socktype;
+ results[i].ai_protocol = rai->ai_protocol;
+ results[i].ai_addrlen = rai->ai_addrlen;
+ results[i].ai_addr = (sockaddr *)malloc(results[i].ai_addrlen);
+ memcpy(results[i].ai_addr, rai->ai_addr, results[i].ai_addrlen);
+ results[i].ai_canonname = (i == 0) ? strdup(r->ai_canonname) : 0;
+ results[i].ai_next = (int)i+1 < r->addrCount ? &results[i+1] : 0;
+ fprintf(stderr, "%d: ai_flags=%d, ai_family=%d, ai_socktype=%d, ai_protocol=%d, ai_addrlen=%d, ai_addr=", (int)i, results[i].ai_flags, results[i].ai_family, results[i].ai_socktype, results[i].ai_protocol, results[i].ai_addrlen);
+ for(size_t j = 0; j < results[i].ai_addrlen; ++j)
+ fprintf(stderr, " %02X", ((uint8_t*)results[i].ai_addr)[j]);
+ fprintf(stderr, ",ai_canonname=%s, ai_next=%p\n", results[i].ai_canonname, results[i].ai_next);
+ raiAddr += sizeof(ResAddrinfo) + rai->ai_addrlen;
+ }
+ *res = results;
+ }
+ }
+ else
+ {
+ errno = b->data->errno_;
+ if (res) *res = 0;
+ }
+ free_call_result(b);
+
+ return ret;
+}
+
+void em_proxy_freeaddrinfo(struct addrinfo *res)
+{
+ if (bridgeState != BRIDGE_OPEN) {
+ return freeaddrinfo(res);
+ }
+
+ for(addrinfo *r = res; r; r = r->ai_next)
+ {
+ free(r->ai_canonname);
+ free(r->ai_addr);
+ }
+ free(res);
+}
+
+int em_proxy_getnameinfo(const struct sockaddr *addr, socklen_t addrlen, char *host, socklen_t hostlen, char *serv, socklen_t servlen, int flags)
+{
+ return getnameinfo(addr, addrlen, host, hostlen, serv, servlen, flags);
+}
+
+int em_proxy_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)
+{
+ if (bridgeState != BRIDGE_OPEN) {
+ return select(nfds, readfds, writefds, exceptfds, timeout);
+ }
+
+ // TODO: implement
+ return -1;
+}
+
+#endif /* __EMSCRIPTEN__ */
diff --git a/src/network/core/em_proxy.h b/src/network/core/em_proxy.h
new file mode 100644
index 0000000000..dfd26d40c4
--- /dev/null
+++ b/src/network/core/em_proxy.h
@@ -0,0 +1,56 @@
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD 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, version 2.
+ * OpenTTD 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 OpenTTD. If not, see .
+ */
+
+/**
+ * @file em_proxy.h Emscripten socket proxy
+ */
+
+#ifndef NETWORK_CORE_EM_PROXY_H
+#define NETWORK_CORE_EM_PROXY_H
+
+int em_proxy_initialize_network(void);
+
+int em_proxy_socket(int domain, int type, int protocol);
+int em_proxy_closesocket(int socket);
+int em_proxy_shutdown(int socket, int how);
+int em_proxy_bind(int socket, const struct sockaddr *address, socklen_t address_len);
+int em_proxy_connect(int socket, const struct sockaddr *address, socklen_t address_len);
+int em_proxy_listen(int socket, int backlog);
+int em_proxy_accept(int socket, struct sockaddr *address, socklen_t *address_len);
+ssize_t em_proxy_send(int socket, const void *message, size_t length, int flags);
+ssize_t em_proxy_recv(int socket, void *buffer, size_t length, int flags);
+ssize_t em_proxy_sendto(int socket, const void *message, size_t length, int flags, const struct sockaddr *dest_addr, socklen_t dest_len);
+ssize_t em_proxy_recvfrom(int socket, void *buffer, size_t length, int flags, struct sockaddr *address, socklen_t *address_len);
+int em_proxy_getsockopt(int socket, int level, int option_name, void *option_value, socklen_t *option_len);
+int em_proxy_setsockopt(int socket, int level, int option_name, const void *option_value, socklen_t option_len);
+int em_proxy_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res);
+void em_proxy_freeaddrinfo(struct addrinfo *res);
+int em_proxy_getnameinfo(const struct sockaddr *addr, socklen_t addrlen, char *host, socklen_t hostlen, char *serv, socklen_t servlen, int flags);
+int em_proxy_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
+
+#ifndef EM_PROXY_NO_MACROS
+# define socket em_proxy_socket
+# undef closesocket
+# define closesocket em_proxy_closesocket
+# define shutdown em_proxy_shutdown
+# define bind em_proxy_bind
+# define connect em_proxy_connect
+# define listen em_proxy_listen
+# define accept em_proxy_accept
+# define send em_proxy_send
+# define recv em_proxy_recv
+# define sendto em_proxy_sendto
+# define recvfrom em_proxy_recvfrom
+# define getsockopt em_proxy_getsockopt
+# define setsockopt em_proxy_setsockopt
+# define getaddrinfo em_proxy_getaddrinfo
+# define freeaddrinfo em_proxy_freeaddrinfo
+# define getnameinfo em_proxy_getnameinfo
+# define select em_proxy_select
+#endif /* EM_PROXY_NO_MACROS */
+
+#endif /* NETWORK_CORE_EM_PROXY_H */
diff --git a/src/network/core/os_abstraction.cpp b/src/network/core/os_abstraction.cpp
index b2d3475d01..814ac04939 100644
--- a/src/network/core/os_abstraction.cpp
+++ b/src/network/core/os_abstraction.cpp
@@ -150,13 +150,9 @@ bool SetNonBlocking(SOCKET d)
*/
bool SetNoDelay(SOCKET d)
{
-#ifdef __EMSCRIPTEN__
- return true;
-#else
int flags = 1;
/* The (const char*) cast is needed for windows */
return setsockopt(d, IPPROTO_TCP, TCP_NODELAY, (const char *)&flags, sizeof(flags)) == 0;
-#endif
}
/**
diff --git a/src/network/core/os_abstraction.h b/src/network/core/os_abstraction.h
index bde1f6bedd..4ef7994bd7 100644
--- a/src/network/core/os_abstraction.h
+++ b/src/network/core/os_abstraction.h
@@ -174,4 +174,8 @@ NetworkError GetSocketError(SOCKET d);
static_assert(sizeof(in_addr) == 4); ///< IPv4 addresses should be 4 bytes.
static_assert(sizeof(in6_addr) == 16); ///< IPv6 addresses should be 16 bytes.
+#if defined(__EMSCRIPTEN__)
+# include "em_proxy.h"
+#endif /* __EMSCRIPTEN__ */
+
#endif /* NETWORK_CORE_OS_ABSTRACTION_H */