#include "Net.h" #include #include #include namespace bs::net { namespace { bool gInit = false; constexpr const char* kQuery = "BSAFARI?"; constexpr const char* kReply = "BSAFARI!"; // ENetBuffer's field order differs per platform (it aliases iovec / WSABUF). ENetBuffer buffer(void* data, size_t size) { ENetBuffer b; b.data = data; b.dataLength = size; return b; } } // namespace bool initialize() { if (gInit) return true; if (enet_initialize() != 0) return false; gInit = true; return true; } void shutdown() { if (gInit) enet_deinitialize(); gInit = false; } Host::~Host() { if (host_) { for (auto& [id, p] : peers_) enet_peer_disconnect_now(p, 0); enet_host_flush(host_); enet_host_destroy(host_); } } bool Host::listen(uint16_t port, size_t maxPeers) { ENetAddress addr; addr.host = ENET_HOST_ANY; addr.port = port; host_ = enet_host_create(&addr, maxPeers, kChannelCount, 0, 0); return host_ != nullptr; } bool Host::connect(const std::string& address, uint16_t port) { if (!host_) host_ = enet_host_create(nullptr, 1, kChannelCount, 0, 0); if (!host_) return false; ENetAddress addr; if (enet_address_set_host(&addr, address.c_str()) != 0) return false; addr.port = port; ENetPeer* p = enet_host_connect(host_, &addr, kChannelCount, 0); if (!p) return false; uint32_t id = nextId_++; p->data = reinterpret_cast(uintptr_t(id)); peers_[id] = p; enet_peer_timeout(p, 0, 4000, 8000); return true; } bool Host::poll(Event& ev, uint32_t timeoutMs) { ev = Event{}; if (!host_) return false; ENetEvent e; int r = enet_host_service(host_, &e, timeoutMs); if (r <= 0) return false; switch (e.type) { case ENET_EVENT_TYPE_CONNECT: { uint32_t id = uint32_t(uintptr_t(e.peer->data)); if (!id) { // incoming connection id = nextId_++; e.peer->data = reinterpret_cast(uintptr_t(id)); peers_[id] = e.peer; enet_peer_timeout(e.peer, 0, 5000, 10000); } ev.type = Event::Connect; ev.peer = id; return true; } case ENET_EVENT_TYPE_DISCONNECT: { uint32_t id = uint32_t(uintptr_t(e.peer->data)); peers_.erase(id); e.peer->data = nullptr; ev.type = Event::Disconnect; ev.peer = id; return true; } case ENET_EVENT_TYPE_RECEIVE: { ev.type = Event::Receive; ev.peer = uint32_t(uintptr_t(e.peer->data)); ev.data.assign(e.packet->data, e.packet->data + e.packet->dataLength); enet_packet_destroy(e.packet); return true; } default: return false; } } void Host::send(uint32_t peer, Channel ch, const std::vector& data) { auto it = peers_.find(peer); if (it == peers_.end() || data.empty()) return; ENetPacket* pk = enet_packet_create(data.data(), data.size(), ch == Reliable ? ENET_PACKET_FLAG_RELIABLE : 0); if (enet_peer_send(it->second, ch, pk) != 0) enet_packet_destroy(pk); } void Host::disconnect(uint32_t peer) { auto it = peers_.find(peer); if (it != peers_.end()) enet_peer_disconnect_later(it->second, 0); } void Host::flush() { if (host_) enet_host_flush(host_); } std::string Host::address(uint32_t peer) const { auto it = peers_.find(peer); if (it == peers_.end()) return "?"; char buf[64] = {}; enet_address_get_host_ip(&it->second->address, buf, sizeof buf); return buf; } uint32_t Host::roundTripMs(uint32_t peer) const { auto it = peers_.find(peer); return it == peers_.end() ? 0 : it->second->roundTripTime; } // ------------------------------------------------------------------ LAN discovery Beacon::~Beacon() { if (sock_ >= 0) enet_socket_destroy(ENetSocket(sock_)); } bool Beacon::open(uint16_t discoveryPort) { ENetSocket s = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM); if (s == ENET_SOCKET_NULL) return false; enet_socket_set_option(s, ENET_SOCKOPT_REUSEADDR, 1); enet_socket_set_option(s, ENET_SOCKOPT_NONBLOCK, 1); enet_socket_set_option(s, ENET_SOCKOPT_BROADCAST, 1); ENetAddress addr; addr.host = ENET_HOST_ANY; addr.port = discoveryPort; if (enet_socket_bind(s, &addr) != 0) { enet_socket_destroy(s); return false; } sock_ = intptr_t(s); return true; } void Beacon::poll(uint16_t gamePort, const std::string& serverName, int players) { if (sock_ < 0) return; char buf[256]; for (int i = 0; i < 16; ++i) { ENetAddress from; ENetBuffer b = buffer(buf, sizeof buf - 1); int n = enet_socket_receive(ENetSocket(sock_), &from, &b, 1); if (n <= 0) return; buf[n] = 0; if (std::strncmp(buf, kQuery, std::strlen(kQuery)) != 0) continue; char reply[256]; int len = std::snprintf(reply, sizeof reply, "%s%u|%d|%s", kReply, unsigned(gamePort), players, serverName.substr(0, 120).c_str()); ENetBuffer out = buffer(reply, size_t(len)); enet_socket_send(ENetSocket(sock_), &from, &out, 1); } } Scanner::~Scanner() { if (sock_ >= 0) enet_socket_destroy(ENetSocket(sock_)); } bool Scanner::open() { ENetSocket s = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM); if (s == ENET_SOCKET_NULL) return false; enet_socket_set_option(s, ENET_SOCKOPT_NONBLOCK, 1); enet_socket_set_option(s, ENET_SOCKOPT_BROADCAST, 1); ENetAddress addr; addr.host = ENET_HOST_ANY; addr.port = 0; enet_socket_bind(s, &addr); sock_ = intptr_t(s); return true; } void Scanner::ping(uint16_t discoveryPort) { if (sock_ < 0) return; ENetBuffer b = buffer(const_cast(kQuery), std::strlen(kQuery)); ENetAddress to; to.host = ENET_HOST_BROADCAST; to.port = discoveryPort; enet_socket_send(ENetSocket(sock_), &to, &b, 1); // also ask localhost directly (broadcast does not always loop back) enet_address_set_host(&to, "127.0.0.1"); enet_socket_send(ENetSocket(sock_), &to, &b, 1); } void Scanner::poll(std::vector& found) { if (sock_ < 0) return; char buf[256]; for (int i = 0; i < 32; ++i) { ENetAddress from; ENetBuffer b = buffer(buf, sizeof buf - 1); int n = enet_socket_receive(ENetSocket(sock_), &from, &b, 1); if (n <= 0) return; buf[n] = 0; size_t rl = std::strlen(kReply); if (std::strncmp(buf, kReply, rl) != 0) continue; ServerInfo info; char ip[64] = {}; enet_address_get_host_ip(&from, ip, sizeof ip); info.address = ip; unsigned port = 0; int players = 0; int consumed = 0; if (std::sscanf(buf + rl, "%u|%d|%n", &port, &players, &consumed) < 2) continue; info.port = uint16_t(port); info.players = players; info.name = buf + rl + consumed; bool dup = false; for (ServerInfo& s : found) if (s.port == info.port && (s.address == info.address || s.name == info.name)) { s.players = info.players; dup = true; } if (!dup) found.push_back(info); } } } // namespace bs::net