|
|
|
|
@@ -1,9 +1,11 @@
|
|
|
|
|
// OverteClient.cpp
|
|
|
|
|
#include "OverteClient.hpp"
|
|
|
|
|
|
|
|
|
|
#include <chrono>
|
|
|
|
|
#include <cmath>
|
|
|
|
|
#include <iostream>
|
|
|
|
|
#include <random>
|
|
|
|
|
#include <sstream>
|
|
|
|
|
#include <iomanip>
|
|
|
|
|
#include <glm/gtc/matrix_transform.hpp>
|
|
|
|
|
#include <sys/socket.h>
|
|
|
|
|
#include <netinet/in.h>
|
|
|
|
|
@@ -15,7 +17,38 @@
|
|
|
|
|
|
|
|
|
|
using namespace std::chrono_literals;
|
|
|
|
|
|
|
|
|
|
// Generate a simple UUID-like string for session identification
|
|
|
|
|
static std::string generateUUID() {
|
|
|
|
|
std::random_device rd;
|
|
|
|
|
std::mt19937 gen(rd());
|
|
|
|
|
std::uniform_int_distribution<> dis(0, 255);
|
|
|
|
|
|
|
|
|
|
std::stringstream ss;
|
|
|
|
|
ss << std::hex << std::setfill('0');
|
|
|
|
|
for (int i = 0; i < 16; ++i) {
|
|
|
|
|
if (i == 4 || i == 6 || i == 8 || i == 10) ss << '-';
|
|
|
|
|
ss << std::setw(2) << dis(gen);
|
|
|
|
|
}
|
|
|
|
|
return ss.str();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool OverteClient::connect() {
|
|
|
|
|
// Generate session UUID
|
|
|
|
|
m_sessionUUID = generateUUID();
|
|
|
|
|
std::cout << "[OverteClient] Session UUID: " << m_sessionUUID << std::endl;
|
|
|
|
|
|
|
|
|
|
// Check for authentication credentials from environment
|
|
|
|
|
const char* usernameEnv = std::getenv("OVERTE_USERNAME");
|
|
|
|
|
const char* passwordEnv = std::getenv("OVERTE_PASSWORD");
|
|
|
|
|
if (usernameEnv) m_username = usernameEnv;
|
|
|
|
|
if (passwordEnv) m_password = passwordEnv;
|
|
|
|
|
|
|
|
|
|
if (!m_username.empty()) {
|
|
|
|
|
std::cout << "[OverteClient] Using authentication: username=" << m_username << std::endl;
|
|
|
|
|
} else {
|
|
|
|
|
std::cout << "[OverteClient] No authentication credentials provided (set OVERTE_USERNAME/OVERTE_PASSWORD)" << std::endl;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Parse ws://host:port
|
|
|
|
|
std::string url = m_domainUrl;
|
|
|
|
|
if (url.empty()) url = "ws://127.0.0.1:40102";
|
|
|
|
|
@@ -23,6 +56,13 @@ bool OverteClient::connect() {
|
|
|
|
|
auto colon = url.find(':');
|
|
|
|
|
m_host = colon == std::string::npos ? url : url.substr(0, colon);
|
|
|
|
|
m_port = colon == std::string::npos ? 40102 : std::stoi(url.substr(colon + 1));
|
|
|
|
|
|
|
|
|
|
// Check for environment override for UDP port (domain server UDP port)
|
|
|
|
|
const char* portEnv = std::getenv("OVERTE_UDP_PORT");
|
|
|
|
|
int udpPort = portEnv ? std::atoi(portEnv) : 40104; // Default to 40104 for Overte domain UDP
|
|
|
|
|
|
|
|
|
|
std::cout << "[OverteClient] Connecting to domain at " << m_host
|
|
|
|
|
<< " (HTTP:" << m_port << ", UDP:" << udpPort << ")" << std::endl;
|
|
|
|
|
|
|
|
|
|
// Resolve host:port
|
|
|
|
|
addrinfo hints{}; hints.ai_socktype = SOCK_STREAM; hints.ai_family = AF_UNSPEC;
|
|
|
|
|
@@ -50,10 +90,10 @@ bool OverteClient::connect() {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Setup UDP to target (avatar mixer guess: same port by default)
|
|
|
|
|
// Setup UDP to target (domain server UDP port)
|
|
|
|
|
addrinfo uhints{}; uhints.ai_socktype = SOCK_DGRAM; uhints.ai_family = AF_UNSPEC;
|
|
|
|
|
addrinfo* ures = nullptr;
|
|
|
|
|
int ugai = ::getaddrinfo(m_host.c_str(), std::to_string(m_port).c_str(), &uhints, &ures);
|
|
|
|
|
int ugai = ::getaddrinfo(m_host.c_str(), std::to_string(udpPort).c_str(), &uhints, &ures);
|
|
|
|
|
if (ugai != 0) {
|
|
|
|
|
std::cerr << "[OverteClient] UDP resolve failed: " << gai_strerror(ugai) << std::endl;
|
|
|
|
|
} else {
|
|
|
|
|
@@ -64,7 +104,7 @@ bool OverteClient::connect() {
|
|
|
|
|
std::memcpy(&m_udpAddr, rp->ai_addr, rp->ai_addrlen);
|
|
|
|
|
m_udpAddrLen = rp->ai_addrlen;
|
|
|
|
|
m_udpReady = true;
|
|
|
|
|
std::cout << "[OverteClient] UDP socket ready for " << m_host << ":" << m_port << std::endl;
|
|
|
|
|
std::cout << "[OverteClient] UDP socket ready for " << m_host << ":" << udpPort << std::endl;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
::freeaddrinfo(ures);
|
|
|
|
|
@@ -76,6 +116,12 @@ bool OverteClient::connect() {
|
|
|
|
|
std::cerr << "OverteClient: failed to connect one or more mixers" << std::endl;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Send domain connect request to initiate handshake
|
|
|
|
|
// Start with domain list request - simpler packet
|
|
|
|
|
std::cout << "[OverteClient] Initiating domain handshake..." << std::endl;
|
|
|
|
|
sendDomainConnectRequest();
|
|
|
|
|
sendDomainListRequest();
|
|
|
|
|
|
|
|
|
|
m_useSimulation = (std::getenv("STARWORLD_SIMULATE") != nullptr);
|
|
|
|
|
if (m_useSimulation) {
|
|
|
|
|
@@ -101,10 +147,8 @@ bool OverteClient::connectAvatarMixer() {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool OverteClient::connectEntityServer() {
|
|
|
|
|
// Send DomainList request to discover EntityServer endpoint
|
|
|
|
|
sendDomainListRequest();
|
|
|
|
|
|
|
|
|
|
// Create UDP socket for EntityServer and BIND it to receive packets
|
|
|
|
|
// Entity server connection will be established after DomainList reply
|
|
|
|
|
// For now, create socket and bind to receive packets
|
|
|
|
|
m_entityFd = ::socket(AF_INET, SOCK_DGRAM, 0);
|
|
|
|
|
if (m_entityFd == -1) {
|
|
|
|
|
std::cerr << "[OverteClient] Failed to create EntityServer socket: " << std::strerror(errno) << std::endl;
|
|
|
|
|
@@ -114,30 +158,24 @@ bool OverteClient::connectEntityServer() {
|
|
|
|
|
// Make non-blocking
|
|
|
|
|
::fcntl(m_entityFd, F_SETFL, O_NONBLOCK);
|
|
|
|
|
|
|
|
|
|
// Bind to port 40103 to receive entity packets
|
|
|
|
|
// Bind to ephemeral port (let OS choose) for receiving entity packets
|
|
|
|
|
sockaddr_in bindAddr{};
|
|
|
|
|
bindAddr.sin_family = AF_INET;
|
|
|
|
|
bindAddr.sin_addr.s_addr = INADDR_ANY; // Listen on all interfaces
|
|
|
|
|
bindAddr.sin_port = htons(m_port + 1); // 40103
|
|
|
|
|
bindAddr.sin_addr.s_addr = INADDR_ANY;
|
|
|
|
|
bindAddr.sin_port = 0; // Let OS assign port
|
|
|
|
|
|
|
|
|
|
if (::bind(m_entityFd, reinterpret_cast<sockaddr*>(&bindAddr), sizeof(bindAddr)) == -1) {
|
|
|
|
|
std::cerr << "[OverteClient] Failed to bind EntityServer socket to port " << (m_port + 1)
|
|
|
|
|
<< ": " << std::strerror(errno) << std::endl;
|
|
|
|
|
std::cerr << "[OverteClient] Failed to bind EntityServer socket: " << std::strerror(errno) << std::endl;
|
|
|
|
|
::close(m_entityFd);
|
|
|
|
|
m_entityFd = -1;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Store the target address for sending (if needed)
|
|
|
|
|
m_entityAddr = {};
|
|
|
|
|
sockaddr_in* addr = reinterpret_cast<sockaddr_in*>(&m_entityAddr);
|
|
|
|
|
addr->sin_family = AF_INET;
|
|
|
|
|
addr->sin_port = htons(m_port + 1);
|
|
|
|
|
::inet_pton(AF_INET, m_host.c_str(), &addr->sin_addr);
|
|
|
|
|
m_entityAddrLen = sizeof(sockaddr_in);
|
|
|
|
|
|
|
|
|
|
m_entityServerReady = true;
|
|
|
|
|
std::cout << "[OverteClient] EntityServer socket bound and listening on port " << (m_port + 1) << std::endl;
|
|
|
|
|
// Get the assigned port
|
|
|
|
|
socklen_t addrLen = sizeof(bindAddr);
|
|
|
|
|
if (::getsockname(m_entityFd, reinterpret_cast<sockaddr*>(&bindAddr), &addrLen) == 0) {
|
|
|
|
|
std::cout << "[OverteClient] EntityServer socket bound to port " << ntohs(bindAddr.sin_port) << std::endl;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_entityServer = true;
|
|
|
|
|
return true;
|
|
|
|
|
@@ -152,21 +190,44 @@ bool OverteClient::connectAudioMixer() {
|
|
|
|
|
void OverteClient::poll() {
|
|
|
|
|
if (!m_connected) return;
|
|
|
|
|
|
|
|
|
|
// Try a lightweight UDP ping if ready (placeholder for avatar mixer handshake)
|
|
|
|
|
// Poll domain UDP socket for domain-level packets
|
|
|
|
|
if (m_udpReady && m_udpFd != -1) {
|
|
|
|
|
const char ping[4] = {'P','I','N','G'};
|
|
|
|
|
ssize_t s = ::sendto(m_udpFd, ping, sizeof(ping), 0, reinterpret_cast<sockaddr*>(&m_udpAddr), m_udpAddrLen);
|
|
|
|
|
if (s == -1 && errno != EWOULDBLOCK && errno != EAGAIN) {
|
|
|
|
|
std::cerr << "[OverteClient] UDP send failed: " << std::strerror(errno) << std::endl;
|
|
|
|
|
}
|
|
|
|
|
char buf[1500];
|
|
|
|
|
sockaddr_storage from{}; socklen_t fromlen = sizeof(from);
|
|
|
|
|
ssize_t r = ::recvfrom(m_udpFd, buf, sizeof(buf), 0, reinterpret_cast<sockaddr*>(&from), &fromlen);
|
|
|
|
|
if (r > 0) {
|
|
|
|
|
// Parse as potential domain/avatar packets
|
|
|
|
|
std::cout << "[OverteClient] Domain UDP packet received (" << r << " bytes, type=0x"
|
|
|
|
|
<< std::hex << (int)(unsigned char)buf[0] << std::dec << ")" << std::endl;
|
|
|
|
|
parseEntityPacket(buf, static_cast<size_t>(r));
|
|
|
|
|
std::cout << "[OverteClient] <<< Received domain packet (" << r << " bytes)" << std::endl;
|
|
|
|
|
// Hex dump first 32 bytes for debugging
|
|
|
|
|
std::cout << "[OverteClient] Hex: ";
|
|
|
|
|
for (int i = 0; i < std::min(32, (int)r); ++i) {
|
|
|
|
|
printf("%02x ", (unsigned char)buf[i]);
|
|
|
|
|
}
|
|
|
|
|
std::cout << std::endl;
|
|
|
|
|
parseDomainPacket(buf, static_cast<size_t>(r));
|
|
|
|
|
} else if (r < 0 && errno != EWOULDBLOCK && errno != EAGAIN) {
|
|
|
|
|
// Only log errors that aren't "would block"
|
|
|
|
|
static int errorCount = 0;
|
|
|
|
|
if (++errorCount <= 3) {
|
|
|
|
|
std::cerr << "[OverteClient] UDP recv error: " << strerror(errno) << std::endl;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Send periodic ping to domain to keep connection alive
|
|
|
|
|
static auto lastPing = std::chrono::steady_clock::now();
|
|
|
|
|
static auto lastDomainList = std::chrono::steady_clock::now();
|
|
|
|
|
auto now = std::chrono::steady_clock::now();
|
|
|
|
|
|
|
|
|
|
if (std::chrono::duration_cast<std::chrono::seconds>(now - lastPing).count() >= 1) {
|
|
|
|
|
sendPing(m_udpFd, m_udpAddr, m_udpAddrLen);
|
|
|
|
|
lastPing = now;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Request domain list periodically if not connected
|
|
|
|
|
if (!m_domainConnected && std::chrono::duration_cast<std::chrono::seconds>(now - lastDomainList).count() >= 3) {
|
|
|
|
|
std::cout << "[OverteClient] Retrying domain handshake..." << std::endl;
|
|
|
|
|
sendDomainConnectRequest();
|
|
|
|
|
sendDomainListRequest();
|
|
|
|
|
lastDomainList = now;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -201,6 +262,47 @@ void OverteClient::parseNetworkPackets() {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OverteClient::parseDomainPacket(const char* data, size_t len) {
|
|
|
|
|
if (len < 1) return;
|
|
|
|
|
|
|
|
|
|
unsigned char packetType = static_cast<unsigned char>(data[0]);
|
|
|
|
|
|
|
|
|
|
// Domain-level packet types
|
|
|
|
|
const unsigned char PACKET_TYPE_PING = 0x01;
|
|
|
|
|
const unsigned char PACKET_TYPE_PING_REPLY = 0x02;
|
|
|
|
|
const unsigned char PACKET_TYPE_DOMAIN_LIST = 0x03;
|
|
|
|
|
const unsigned char PACKET_TYPE_DOMAIN_CONNECTION_DENIED = 0x06;
|
|
|
|
|
const unsigned char PACKET_TYPE_DOMAIN_SERVER_REQUIRE_DT = 0x07;
|
|
|
|
|
const unsigned char PACKET_TYPE_DOMAIN_CONNECT_ACK = 0x0A;
|
|
|
|
|
|
|
|
|
|
std::cout << "[OverteClient] Domain packet type: 0x" << std::hex << (int)packetType << std::dec << std::endl;
|
|
|
|
|
|
|
|
|
|
switch (packetType) {
|
|
|
|
|
case PACKET_TYPE_DOMAIN_LIST:
|
|
|
|
|
handleDomainListReply(data + 1, len - 1);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case PACKET_TYPE_DOMAIN_CONNECTION_DENIED:
|
|
|
|
|
handleDomainConnectionDenied(data + 1, len - 1);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case PACKET_TYPE_DOMAIN_CONNECT_ACK:
|
|
|
|
|
std::cout << "[OverteClient] Domain connection acknowledged!" << std::endl;
|
|
|
|
|
m_domainConnected = true;
|
|
|
|
|
// Request domain list after successful connection
|
|
|
|
|
sendDomainListRequest();
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case PACKET_TYPE_PING_REPLY:
|
|
|
|
|
// Keep-alive ping reply
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
std::cout << "[OverteClient] Unknown domain packet type: 0x" << std::hex << (int)packetType << std::dec << std::endl;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OverteClient::parseEntityPacket(const char* data, size_t len) {
|
|
|
|
|
// Overte packet structure (simplified):
|
|
|
|
|
// - Byte 0: PacketType
|
|
|
|
|
@@ -210,31 +312,18 @@ void OverteClient::parseEntityPacket(const char* data, size_t len) {
|
|
|
|
|
|
|
|
|
|
unsigned char packetType = static_cast<unsigned char>(data[0]);
|
|
|
|
|
|
|
|
|
|
// Overte PacketType enum values (reference from protocol documentation)
|
|
|
|
|
// Reference: https://github.com/overte-org/overte/blob/master/libraries/networking/src/udt/PacketHeaders.h
|
|
|
|
|
const unsigned char PACKET_TYPE_DOMAIN_LIST = 0x03;
|
|
|
|
|
const unsigned char PACKET_TYPE_DOMAIN_CONNECTION_DENIED = 0x06;
|
|
|
|
|
const unsigned char PACKET_TYPE_PING = 0x01;
|
|
|
|
|
const unsigned char PACKET_TYPE_PING_REPLY = 0x02;
|
|
|
|
|
|
|
|
|
|
// Entity packet types
|
|
|
|
|
const unsigned char PACKET_TYPE_ENTITY_ADD = 0x10;
|
|
|
|
|
const unsigned char PACKET_TYPE_ENTITY_EDIT = 0x11;
|
|
|
|
|
const unsigned char PACKET_TYPE_ENTITY_ERASE = 0x12;
|
|
|
|
|
const unsigned char PACKET_TYPE_ENTITY_QUERY = 0x15;
|
|
|
|
|
const unsigned char PACKET_TYPE_OCTREE_STATS = 0x16;
|
|
|
|
|
const unsigned char PACKET_TYPE_ENTITY_DATA = 0x41; // Bulk entity data response
|
|
|
|
|
|
|
|
|
|
switch (packetType) {
|
|
|
|
|
case PACKET_TYPE_DOMAIN_LIST:
|
|
|
|
|
handleDomainListReply(data + 1, len - 1);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case PACKET_TYPE_PING_REPLY:
|
|
|
|
|
std::cout << "[OverteClient] Ping reply received from domain" << std::endl;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case PACKET_TYPE_ENTITY_DATA:
|
|
|
|
|
case PACKET_TYPE_ENTITY_ADD: {
|
|
|
|
|
// EntityAdd packet structure (simplified):
|
|
|
|
|
// EntityAdd/EntityData packet structure (simplified):
|
|
|
|
|
// u64 entityID, string name, vec3 position, quat rotation, vec3 dimensions, ...
|
|
|
|
|
if (len < 9) break; // need at least 1+8 bytes
|
|
|
|
|
|
|
|
|
|
@@ -249,7 +338,8 @@ void OverteClient::parseEntityPacket(const char* data, size_t len) {
|
|
|
|
|
}
|
|
|
|
|
if (name.empty()) name = "Entity_" + std::to_string(entityId);
|
|
|
|
|
|
|
|
|
|
// Create entity with a visible position spread out in front of user
|
|
|
|
|
// TODO: Parse full entity properties (position, rotation, dimensions)
|
|
|
|
|
// For now, create entity with a visible position spread out in front of user
|
|
|
|
|
// Position entities in a grid pattern for visibility
|
|
|
|
|
float spacing = 0.5f;
|
|
|
|
|
int index = static_cast<int>(entityId % 10);
|
|
|
|
|
@@ -281,6 +371,7 @@ void OverteClient::parseEntityPacket(const char* data, size_t len) {
|
|
|
|
|
// TODO: parse property flags and update transform
|
|
|
|
|
// For now, mark as updated
|
|
|
|
|
m_updateQueue.push_back(entityId);
|
|
|
|
|
std::cout << "[OverteClient] Entity edited: id=" << entityId << std::endl;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
@@ -301,33 +392,39 @@ void OverteClient::parseEntityPacket(const char* data, size_t len) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case PACKET_TYPE_OCTREE_STATS:
|
|
|
|
|
std::cout << "[OverteClient] Received octree stats" << std::endl;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
// Log unknown packet types for debugging
|
|
|
|
|
if (packetType != PACKET_TYPE_PING && packetType != PACKET_TYPE_PING_REPLY) {
|
|
|
|
|
std::cout << "[OverteClient] Unknown packet type: 0x" << std::hex << (int)packetType << std::dec << std::endl;
|
|
|
|
|
}
|
|
|
|
|
std::cout << "[OverteClient] Unknown entity packet type: 0x" << std::hex << (int)packetType << std::dec << std::endl;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OverteClient::handleDomainListReply(const char* data, size_t len) {
|
|
|
|
|
// DomainList packet contains mixer endpoints
|
|
|
|
|
// Format: sequence of [NodeType:u8][UUID:16bytes][PublicSocket:sockaddr][LocalSocket:sockaddr]
|
|
|
|
|
// Format: [NumNodes:u8] followed by sequence of:
|
|
|
|
|
// [NodeType:u8][UUID:16bytes][PublicSocket:sockaddr][LocalSocket:sockaddr]
|
|
|
|
|
std::cout << "[OverteClient] DomainList reply received (" << len << " bytes)" << std::endl;
|
|
|
|
|
|
|
|
|
|
if (len < 1) return;
|
|
|
|
|
|
|
|
|
|
// Parse number of nodes
|
|
|
|
|
size_t offset = 0;
|
|
|
|
|
unsigned char numNodes = static_cast<unsigned char>(data[0]);
|
|
|
|
|
std::cout << "[OverteClient] Number of assignment clients: " << (int)numNodes << std::endl;
|
|
|
|
|
|
|
|
|
|
while (offset + 1 < len) {
|
|
|
|
|
unsigned char nodeType = data[offset++];
|
|
|
|
|
size_t offset = 1;
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < numNodes && offset < len; ++i) {
|
|
|
|
|
// Read NodeType
|
|
|
|
|
if (offset + 1 > len) break;
|
|
|
|
|
unsigned char nodeType = static_cast<unsigned char>(data[offset++]);
|
|
|
|
|
|
|
|
|
|
// Skip UUID (16 bytes)
|
|
|
|
|
if (offset + 16 > len) break;
|
|
|
|
|
offset += 16;
|
|
|
|
|
|
|
|
|
|
// Read public socket address (sockaddr_in or sockaddr_in6)
|
|
|
|
|
// Read public socket address
|
|
|
|
|
if (offset + sizeof(sockaddr_in) > len) break;
|
|
|
|
|
|
|
|
|
|
sockaddr_in publicAddr;
|
|
|
|
|
@@ -339,7 +436,8 @@ void OverteClient::handleDomainListReply(const char* data, size_t len) {
|
|
|
|
|
offset += sizeof(sockaddr_in);
|
|
|
|
|
|
|
|
|
|
// NodeType values from Overte:
|
|
|
|
|
// 0 = DomainServer, 1 = EntityServer, 2 = Agent, 3 = AudioMixer, 4 = AvatarMixer, 5 = AssetServer, 6 = MessagesMixer, 7 = EntityScriptServer
|
|
|
|
|
// 0 = DomainServer, 1 = EntityServer, 2 = Agent, 3 = AudioMixer,
|
|
|
|
|
// 4 = AvatarMixer, 5 = AssetServer, 6 = MessagesMixer, 7 = EntityScriptServer
|
|
|
|
|
const unsigned char NODE_TYPE_ENTITY_SERVER = 1;
|
|
|
|
|
const unsigned char NODE_TYPE_AVATAR_MIXER = 4;
|
|
|
|
|
const unsigned char NODE_TYPE_AUDIO_MIXER = 3;
|
|
|
|
|
@@ -348,12 +446,24 @@ void OverteClient::handleDomainListReply(const char* data, size_t len) {
|
|
|
|
|
inet_ntop(AF_INET, &publicAddr.sin_addr, addrStr, sizeof(addrStr));
|
|
|
|
|
int port = ntohs(publicAddr.sin_port);
|
|
|
|
|
|
|
|
|
|
std::cout << "[OverteClient] Mixer discovered: type=" << (int)nodeType
|
|
|
|
|
<< " addr=" << addrStr << ":" << port << std::endl;
|
|
|
|
|
const char* nodeTypeName = "Unknown";
|
|
|
|
|
switch (nodeType) {
|
|
|
|
|
case 0: nodeTypeName = "DomainServer"; break;
|
|
|
|
|
case NODE_TYPE_ENTITY_SERVER: nodeTypeName = "EntityServer"; break;
|
|
|
|
|
case 2: nodeTypeName = "Agent"; break;
|
|
|
|
|
case NODE_TYPE_AUDIO_MIXER: nodeTypeName = "AudioMixer"; break;
|
|
|
|
|
case NODE_TYPE_AVATAR_MIXER: nodeTypeName = "AvatarMixer"; break;
|
|
|
|
|
case 5: nodeTypeName = "AssetServer"; break;
|
|
|
|
|
case 6: nodeTypeName = "MessagesMixer"; break;
|
|
|
|
|
case 7: nodeTypeName = "EntityScriptServer"; break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (nodeType == NODE_TYPE_ENTITY_SERVER && !m_entityServerReady) {
|
|
|
|
|
std::cout << "[OverteClient] Assignment: " << nodeTypeName
|
|
|
|
|
<< " at " << addrStr << ":" << port << std::endl;
|
|
|
|
|
|
|
|
|
|
if (nodeType == NODE_TYPE_ENTITY_SERVER) {
|
|
|
|
|
// Update EntityServer connection to use discovered address
|
|
|
|
|
std::cout << "[OverteClient] Using discovered EntityServer at " << addrStr << ":" << port << std::endl;
|
|
|
|
|
std::cout << "[OverteClient] Connecting to EntityServer at " << addrStr << ":" << port << std::endl;
|
|
|
|
|
|
|
|
|
|
// Update target address for EntityServer
|
|
|
|
|
sockaddr_in* entityAddr = reinterpret_cast<sockaddr_in*>(&m_entityAddr);
|
|
|
|
|
@@ -362,14 +472,89 @@ void OverteClient::handleDomainListReply(const char* data, size_t len) {
|
|
|
|
|
entityAddr->sin_addr = publicAddr.sin_addr;
|
|
|
|
|
m_entityAddrLen = sizeof(sockaddr_in);
|
|
|
|
|
|
|
|
|
|
m_entityServerReady = true;
|
|
|
|
|
|
|
|
|
|
// Send EntityQuery to request all entities
|
|
|
|
|
sendEntityQuery();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OverteClient::handleDomainConnectionDenied(const char* data, size_t len) {
|
|
|
|
|
std::cerr << "[OverteClient] Domain connection DENIED!" << std::endl;
|
|
|
|
|
|
|
|
|
|
// Parse reason if available
|
|
|
|
|
if (len > 0) {
|
|
|
|
|
std::string reason(data, len);
|
|
|
|
|
std::cerr << "[OverteClient] Reason: " << reason << std::endl;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_domainConnected = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OverteClient::sendDomainConnectRequest() {
|
|
|
|
|
if (!m_udpReady || m_udpFd == -1) return;
|
|
|
|
|
|
|
|
|
|
// DomainConnectRequest packet (PacketType 0x04)
|
|
|
|
|
const unsigned char PACKET_TYPE_DOMAIN_CONNECT_REQUEST = 0x04;
|
|
|
|
|
|
|
|
|
|
// Build packet with authentication
|
|
|
|
|
// Format: [PacketType][ProtocolVersion:string][HardwareAddress:string][MachineFingerprint:string][Username:string][Password:string]
|
|
|
|
|
std::string protocolVersion = "Starworld-0.1";
|
|
|
|
|
std::string hardwareAddress = m_sessionUUID;
|
|
|
|
|
std::string machineFingerprint = m_sessionUUID; // Use session UUID as fingerprint
|
|
|
|
|
|
|
|
|
|
std::vector<char> packet;
|
|
|
|
|
packet.push_back(static_cast<char>(PACKET_TYPE_DOMAIN_CONNECT_REQUEST));
|
|
|
|
|
|
|
|
|
|
// Helper lambda to add length-prefixed string
|
|
|
|
|
auto addString = [&packet](const std::string& str) {
|
|
|
|
|
if (str.size() > 255) {
|
|
|
|
|
packet.push_back(static_cast<char>(255));
|
|
|
|
|
packet.insert(packet.end(), str.begin(), str.begin() + 255);
|
|
|
|
|
} else {
|
|
|
|
|
packet.push_back(static_cast<char>(str.size()));
|
|
|
|
|
packet.insert(packet.end(), str.begin(), str.end());
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Add protocol version
|
|
|
|
|
addString(protocolVersion);
|
|
|
|
|
|
|
|
|
|
// Add hardware address (session UUID)
|
|
|
|
|
addString(hardwareAddress);
|
|
|
|
|
|
|
|
|
|
// Add machine fingerprint
|
|
|
|
|
addString(machineFingerprint);
|
|
|
|
|
|
|
|
|
|
// Add username (empty if not authenticated)
|
|
|
|
|
addString(m_username);
|
|
|
|
|
|
|
|
|
|
// Add password/token (empty if not authenticated)
|
|
|
|
|
addString(m_password);
|
|
|
|
|
|
|
|
|
|
ssize_t s = ::sendto(m_udpFd, packet.data(), packet.size(), 0,
|
|
|
|
|
reinterpret_cast<sockaddr*>(&m_udpAddr), m_udpAddrLen);
|
|
|
|
|
if (s > 0) {
|
|
|
|
|
std::cout << "[OverteClient] Domain connect request sent (" << s << " bytes)" << std::endl;
|
|
|
|
|
std::cout << "[OverteClient] Protocol: " << protocolVersion << std::endl;
|
|
|
|
|
std::cout << "[OverteClient] Session: " << hardwareAddress << std::endl;
|
|
|
|
|
if (!m_username.empty()) {
|
|
|
|
|
std::cout << "[OverteClient] Username: " << m_username << std::endl;
|
|
|
|
|
}
|
|
|
|
|
// Hex dump first 32 bytes
|
|
|
|
|
std::cout << "[OverteClient] >>> Hex: ";
|
|
|
|
|
for (size_t i = 0; i < std::min(size_t(32), packet.size()); ++i) {
|
|
|
|
|
printf("%02x ", (unsigned char)packet[i]);
|
|
|
|
|
}
|
|
|
|
|
std::cout << std::endl;
|
|
|
|
|
} else {
|
|
|
|
|
std::cerr << "[OverteClient] Failed to send domain connect request: " << strerror(errno) << std::endl;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OverteClient::sendDomainListRequest() {
|
|
|
|
|
// Send DomainList request packet (PacketType 0x02 typically)
|
|
|
|
|
// Send DomainList request packet (PacketType 0x02)
|
|
|
|
|
if (!m_udpReady || m_udpFd == -1) return;
|
|
|
|
|
|
|
|
|
|
const unsigned char PACKET_TYPE_DOMAIN_LIST_REQUEST = 0x02;
|
|
|
|
|
@@ -379,6 +564,56 @@ void OverteClient::sendDomainListRequest() {
|
|
|
|
|
reinterpret_cast<sockaddr*>(&m_udpAddr), m_udpAddrLen);
|
|
|
|
|
if (s > 0) {
|
|
|
|
|
std::cout << "[OverteClient] DomainList request sent" << std::endl;
|
|
|
|
|
} else {
|
|
|
|
|
std::cerr << "[OverteClient] Failed to send domain list request: " << strerror(errno) << std::endl;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OverteClient::sendPing(int fd, const sockaddr_storage& addr, socklen_t addrLen) {
|
|
|
|
|
const unsigned char PACKET_TYPE_PING = 0x01;
|
|
|
|
|
|
|
|
|
|
// Ping packet: [PacketType][Timestamp:u64][PingType:u8]
|
|
|
|
|
char packet[1 + 8 + 1];
|
|
|
|
|
packet[0] = static_cast<char>(PACKET_TYPE_PING);
|
|
|
|
|
|
|
|
|
|
// Add timestamp (microseconds since epoch)
|
|
|
|
|
auto now = std::chrono::system_clock::now();
|
|
|
|
|
auto micros = std::chrono::duration_cast<std::chrono::microseconds>(now.time_since_epoch()).count();
|
|
|
|
|
std::memcpy(packet + 1, µs, 8);
|
|
|
|
|
|
|
|
|
|
// Ping type (0 = local, 1 = public)
|
|
|
|
|
packet[9] = 0;
|
|
|
|
|
|
|
|
|
|
ssize_t s = ::sendto(fd, packet, sizeof(packet), 0,
|
|
|
|
|
reinterpret_cast<const sockaddr*>(&addr), addrLen);
|
|
|
|
|
if (s < 0 && errno != EWOULDBLOCK && errno != EAGAIN) {
|
|
|
|
|
std::cerr << "[OverteClient] Ping send failed: " << strerror(errno) << std::endl;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OverteClient::sendEntityQuery() {
|
|
|
|
|
if (m_entityFd < 0 || !m_entityServerReady) return;
|
|
|
|
|
|
|
|
|
|
const unsigned char PACKET_TYPE_ENTITY_QUERY = 0x15;
|
|
|
|
|
|
|
|
|
|
// EntityQuery packet structure (simplified):
|
|
|
|
|
// [PacketType:u8][ConicalViews:bool][CameraFrustum if ConicalViews=true]
|
|
|
|
|
// For simplicity, send with ConicalViews=false to request all entities
|
|
|
|
|
|
|
|
|
|
std::vector<char> packet;
|
|
|
|
|
packet.push_back(static_cast<char>(PACKET_TYPE_ENTITY_QUERY));
|
|
|
|
|
packet.push_back(0); // ConicalViews = false
|
|
|
|
|
|
|
|
|
|
// With ConicalViews=false, we're requesting all entities
|
|
|
|
|
// Additional octree query parameters can be added here
|
|
|
|
|
|
|
|
|
|
ssize_t sent = sendto(m_entityFd, packet.data(), packet.size(), 0,
|
|
|
|
|
reinterpret_cast<const sockaddr*>(&m_entityAddr), m_entityAddrLen);
|
|
|
|
|
|
|
|
|
|
if (sent > 0) {
|
|
|
|
|
std::cout << "[OverteClient] Sent EntityQuery to EntityServer" << std::endl;
|
|
|
|
|
} else {
|
|
|
|
|
std::cerr << "[OverteClient] Failed to send EntityQuery: " << strerror(errno) << std::endl;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|