BIGGERBIGGER

This commit is contained in:
2026-10-18 11:07:51 -04:00
commit aff6a4f902
1003 changed files with 254388 additions and 0 deletions
+325
View File
@@ -0,0 +1,325 @@
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
#include "Net.h"
#include <algorithm>
#include <chrono>
#include <cstring>
#include <iostream>
#ifndef SIO_UDP_CONNRESET
#define SIO_UDP_CONNRESET _WSAIOW(IOC_VENDOR, 12)
#endif
namespace {
constexpr uint32_t NET_MAGIC = 0x45494946; // "EIIF"
constexpr uint16_t NET_VERSION = 2;
constexpr unsigned short NET_PORT = 27015;
constexpr double NET_TIMEOUT = 2.0; // seconds of silence before a player is dropped
// One datagram = Header followed by 'count' Entry records.
// Client -> host: count 1 (the client's own state, id ignored).
// Host -> client: one entry per other player, host included (id 0).
#pragma pack(push, 1)
struct Header {
uint32_t magic;
uint16_t version;
uint32_t seq;
uint8_t count;
};
struct Entry {
uint8_t id;
float pos[3];
float rot[4]; // x, y, z, w
uint8_t sliding;
};
#pragma pack(pop)
constexpr int MAX_ENTRIES = Lan::MaxPlayers;
constexpr int MAX_DATAGRAM = (int)sizeof(Header) + MAX_ENTRIES * (int)sizeof(Entry);
double Now() {
using namespace std::chrono;
return duration<double>(steady_clock::now().time_since_epoch()).count();
}
Entry MakeEntry(uint8_t id, const NetState& s) {
Entry e;
e.id = id;
e.pos[0] = s.position.x; e.pos[1] = s.position.y; e.pos[2] = s.position.z;
e.rot[0] = s.rotation.x; e.rot[1] = s.rotation.y; e.rot[2] = s.rotation.z; e.rot[3] = s.rotation.w;
e.sliding = s.sliding ? 1 : 0;
return e;
}
NetState ReadEntry(const Entry& e) {
NetState s;
s.position = glm::vec3(e.pos[0], e.pos[1], e.pos[2]);
s.rotation = glm::quat(e.rot[3], e.rot[0], e.rot[1], e.rot[2]); // glm ctor order is w, x, y, z
s.sliding = e.sliding != 0;
return s;
}
bool SameAddress(const sockaddr_in& a, const sockaddr_in& b) {
return a.sin_addr.s_addr == b.sin_addr.s_addr && a.sin_port == b.sin_port;
}
}
struct Lan::Impl {
struct Peer {
sockaddr_in addr{};
uint8_t id = 0;
double lastRecv = 0.0;
uint32_t lastSeq = 0;
bool started = false; // has sent at least one datagram (so lastSeq is valid)
NetState state;
bool hasState = false;
};
SOCKET sock = INVALID_SOCKET;
bool wsaStarted = false;
uint32_t sendSeq = 0;
// Host
std::vector<Peer> peers;
// Client
sockaddr_in hostAddr{};
bool anyRecv = false;
uint32_t lastSeq = 0;
double lastRecvTime = 0.0;
void SendDatagram(const sockaddr_in& to, const std::vector<Entry>& entries) {
char buf[MAX_DATAGRAM];
Header h;
h.magic = NET_MAGIC;
h.version = NET_VERSION;
h.seq = ++sendSeq;
h.count = (uint8_t)entries.size();
std::memcpy(buf, &h, sizeof(h));
std::memcpy(buf + sizeof(h), entries.data(), entries.size() * sizeof(Entry));
sendto(sock, buf, (int)(sizeof(h) + entries.size() * sizeof(Entry)), 0,
(const sockaddr*)&to, (int)sizeof(to));
}
};
Lan::Lan() : impl(new Impl()) {}
Lan::~Lan() {
Stop();
delete impl;
}
bool Lan::Start(Mode m, const std::string& hostIp) {
if (m == Mode::Off) return false;
WSADATA wsa;
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) {
std::cout << "[net] WSAStartup failed\n";
return false;
}
impl->wsaStarted = true;
impl->sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (impl->sock == INVALID_SOCKET) {
std::cout << "[net] socket() failed: " << WSAGetLastError() << "\n";
Stop();
return false;
}
u_long nonBlocking = 1;
ioctlsocket(impl->sock, FIONBIO, &nonBlocking);
// Don't let an ICMP "port unreachable" (other side not up yet) kill recvfrom with WSAECONNRESET
BOOL newBehavior = FALSE;
DWORD bytes = 0;
WSAIoctl(impl->sock, SIO_UDP_CONNRESET, &newBehavior, sizeof(newBehavior), NULL, 0, &bytes, NULL, NULL);
sockaddr_in local{};
local.sin_family = AF_INET;
local.sin_addr.s_addr = htonl(INADDR_ANY);
local.sin_port = htons(m == Mode::Host ? NET_PORT : 0); // clients use any free port
if (bind(impl->sock, (sockaddr*)&local, sizeof(local)) == SOCKET_ERROR) {
std::cout << "[net] bind() failed: " << WSAGetLastError()
<< " (is another copy already hosting on this PC?)\n";
Stop();
return false;
}
if (m == Mode::Client) {
impl->hostAddr.sin_family = AF_INET;
impl->hostAddr.sin_port = htons(NET_PORT);
if (inet_pton(AF_INET, hostIp.c_str(), &impl->hostAddr.sin_addr) != 1) {
std::cout << "[net] '" << hostIp << "' is not a valid IPv4 address\n";
Stop();
return false;
}
}
mode = m;
active = true;
return true;
}
void Lan::Stop() {
if (impl->sock != INVALID_SOCKET) {
closesocket(impl->sock);
impl->sock = INVALID_SOCKET;
}
if (impl->wsaStarted) {
WSACleanup();
impl->wsaStarted = false;
}
impl->peers.clear();
impl->anyRecv = false;
active = false;
mode = Mode::Off;
}
void Lan::Send(const NetState& s) {
if (!active) return;
if (mode == Mode::Client) {
impl->SendDatagram(impl->hostAddr, { MakeEntry(0, s) });
return;
}
// Host: to each player, send the host's state plus every other player's latest state
for (const Impl::Peer& to : impl->peers) {
std::vector<Entry> entries;
entries.push_back(MakeEntry(0, s));
for (const Impl::Peer& other : impl->peers) {
if (other.id != to.id && other.hasState && (int)entries.size() < MAX_ENTRIES)
entries.push_back(MakeEntry(other.id, other.state));
}
impl->SendDatagram(to.addr, entries);
}
}
void Lan::Receive(std::vector<NetUpdate>& out) {
if (!active) return;
char buf[MAX_DATAGRAM + 64];
for (int i = 0; i < 512; i++) { // bounded so a flood can't stall the frame
sockaddr_in from{};
int fromLen = (int)sizeof(from);
int r = recvfrom(impl->sock, buf, (int)sizeof(buf), 0, (sockaddr*)&from, &fromLen);
if (r == SOCKET_ERROR) {
if (WSAGetLastError() == WSAEWOULDBLOCK) break;
continue;
}
if (r < (int)sizeof(Header)) continue;
Header h;
std::memcpy(&h, buf, sizeof(h));
if (h.magic != NET_MAGIC || h.version != NET_VERSION) continue;
if (h.count == 0 || h.count > MAX_ENTRIES) continue;
if (r != (int)(sizeof(Header) + h.count * sizeof(Entry))) continue;
const double now = Now();
if (mode == Mode::Host) {
// Find this sender, or admit them as a new player
Impl::Peer* peer = nullptr;
for (Impl::Peer& p : impl->peers)
if (SameAddress(p.addr, from)) { peer = &p; break; }
if (!peer) {
if ((int)impl->peers.size() >= MaxPlayers - 1) continue; // pit is full
uint8_t id = 1;
while (std::any_of(impl->peers.begin(), impl->peers.end(),
[&](const Impl::Peer& p) { return p.id == id; }))
id++;
Impl::Peer np;
np.addr = from;
np.id = id;
impl->peers.push_back(np);
peer = &impl->peers.back();
std::cout << "[net] player " << (int)id << " joined\n";
}
if (peer->started && (int32_t)(h.seq - peer->lastSeq) <= 0) continue; // stale / duplicate
peer->started = true;
peer->lastSeq = h.seq;
peer->lastRecv = now;
Entry e;
std::memcpy(&e, buf + sizeof(Header), sizeof(Entry));
peer->state = ReadEntry(e);
peer->hasState = true;
NetUpdate u;
u.id = peer->id;
u.state = peer->state;
out.push_back(u);
}
else {
if (!SameAddress(from, impl->hostAddr)) continue; // only listen to the host
// After a long silence the host may have restarted (sequence back to 1): accept anything
if (impl->anyRecv && now - impl->lastRecvTime >= NET_TIMEOUT) impl->anyRecv = false;
if (impl->anyRecv && (int32_t)(h.seq - impl->lastSeq) <= 0) continue;
impl->anyRecv = true;
impl->lastSeq = h.seq;
impl->lastRecvTime = now;
for (int k = 0; k < h.count; k++) {
Entry e;
std::memcpy(&e, buf + sizeof(Header) + k * sizeof(Entry), sizeof(Entry));
NetUpdate u;
u.id = e.id;
u.state = ReadEntry(e);
out.push_back(u);
}
}
}
// Host: forget players who stopped sending
if (mode == Mode::Host) {
const double now = Now();
for (size_t i = 0; i < impl->peers.size();) {
if (now - impl->peers[i].lastRecv > NET_TIMEOUT) {
std::cout << "[net] player " << (int)impl->peers[i].id << " left\n";
impl->peers.erase(impl->peers.begin() + i);
}
else i++;
}
}
}
bool Lan::IsConnected() const {
if (!active) return false;
if (mode == Mode::Host) return !impl->peers.empty();
return impl->anyRecv && (Now() - impl->lastRecvTime) < NET_TIMEOUT;
}
int Lan::PeerCount() const {
return (int)impl->peers.size();
}
std::string Lan::LocalAddresses() {
WSADATA wsa;
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) return "";
std::string result;
char name[256];
if (gethostname(name, sizeof(name)) == 0) {
addrinfo hints{};
hints.ai_family = AF_INET;
addrinfo* list = nullptr;
if (getaddrinfo(name, nullptr, &hints, &list) == 0) {
for (addrinfo* a = list; a; a = a->ai_next) {
char ip[INET_ADDRSTRLEN];
const sockaddr_in* sa = (const sockaddr_in*)a->ai_addr;
if (inet_ntop(AF_INET, &sa->sin_addr, ip, sizeof(ip)))
result += std::string(result.empty() ? "" : ", ") + ip;
}
freeaddrinfo(list);
}
}
WSACleanup();
return result;
}