blob: 1c41eaf34163c59f8d3a972a77ce3b03b9f092c1 [file] [log] [blame]
#include <iostream>
#include "Multiplayer.hpp"
#include "engine/api_private.hpp"
using namespace blit;
Multiplayer::Multiplayer(Mode mode, const std::string &address) : mode(mode), address(address) {
// shouldn't fail unless we ran out of memory
sock_set = SDLNet_AllocSocketSet(2);
}
Multiplayer::~Multiplayer() {
if(sock_set)
SDLNet_FreeSocketSet(sock_set);
if(socket)
SDLNet_TCP_Close(socket);
if(listen_socket)
SDLNet_TCP_Close(listen_socket);
}
void Multiplayer::update() {
if(!enabled)
return;
if(!socket && !listen_socket) {
// attempt to reconnect
auto now = SDL_GetTicks();
if((now - last_connect_time) > retry_interval) {
setup();
last_connect_time = now;
}
return;
}
int num_ready;
while((num_ready = SDLNet_CheckSockets(sock_set, 0))) {
if(num_ready == -1) {
std::cerr << "Failed to check socket: " << SDLNet_GetError() << std::endl;
return;
}
if(listen_socket && SDLNet_SocketReady(listen_socket)) {
// new connection
socket = SDLNet_TCP_Accept(listen_socket);
if(socket) {
auto remote_addr = SDLNet_TCP_GetPeerAddress(socket);
auto ip = SDL_SwapBE32(remote_addr->host);
std::cout << (ip >> 24) << "." << ((ip >> 16) & 0xFF) << "." << ((ip >> 8) & 0xFF) << "." << (ip & 0xFF) << " connected" << std::endl;
SDLNet_TCP_AddSocket(sock_set, socket);
SDLNet_TCP_Send(socket, "32BLMLTI\1", 9);
// stop listening now
stop_listening();
}
}
if(!socket || !SDLNet_SocketReady(socket))
return;
if(!recv_buf) {
// read header and setup
auto read = SDLNet_TCP_Recv(socket, head_buf + head_off, 8 - head_off);
if(read <= 0) {
disconnect();
return;
}
head_off += read;
if(head_off < 8)
continue;
if(memcmp(head_buf, "32BLUSER", 8) == 0) {
// get the length
int off = 0;
do {
read = SDLNet_TCP_Recv(socket, head_buf + off, 2 - off);
if(read <= 0) {
disconnect();
return;
}
off += read;
} while(off != 2);
recv_len = head_buf[0] | (head_buf[1] << 8);
recv_buf = new uint8_t[recv_len];
recv_off = 0;
head_off = 0;
} else if(memcmp(head_buf, "32BLMLTI", 8) == 0) {
// handle the handshake packet
SDLNet_TCP_Recv(socket, head_buf, 1);
handshake = head_buf[0] != 0;
if(mode == Mode::Connect && head_buf[0] == 1)
SDLNet_TCP_Send(socket, "32BLMLTI\2", 9);
head_off = 0;
} else {
std::cerr << "Unexpected header: " << std::string(reinterpret_cast<char *>(head_buf), 8) << std::endl;
head_off = 0;
}
if(!recv_buf || SDLNet_CheckSockets(sock_set, 0) <= 0)
return;
}
auto read = SDLNet_TCP_Recv(socket, recv_buf + recv_off, recv_len - recv_off);
if(read <= 0) {
// failed/disconnected
delete[] recv_buf;
recv_buf = nullptr;
disconnect();
return;
}
recv_off += read;
// got message, pass to user
if(recv_off == recv_len) {
if(api_data.message_received)
api_data.message_received(recv_buf, recv_len);
delete[] recv_buf;
recv_buf = nullptr;
}
}
}
bool Multiplayer::is_connected() const {
return socket != nullptr && handshake;
}
void Multiplayer::set_enabled(bool enabled) {
if(enabled) {
setup();
} else {
disconnect();
if(listen_socket) {
SDLNet_TCP_DelSocket(sock_set, listen_socket);
SDLNet_TCP_Close(listen_socket);
}
}
this->enabled = enabled;
}
void Multiplayer::send_message(const uint8_t *data, uint16_t length) {
if(!socket)
return;
uint8_t head[]{
'3', '2', 'B', 'L',
'U', 'S', 'E', 'R',
static_cast<uint8_t>(length),
static_cast<uint8_t>(length >> 8)
};
if(SDLNet_TCP_Send(socket, head, 10) != 10) {
// failed
disconnect();
return;
}
auto sent = SDLNet_TCP_Send(socket, data, length);
if(sent < length) {
// failed
disconnect();
}
}
void Multiplayer::setup() {
const uint16_t port = 0x32B1;
IPaddress ip;
// try connecting first for auto
if(mode != Mode::Listen) {
if(SDLNet_ResolveHost(&ip, address.c_str(), port) == -1) {
std::cerr << "Failed to resolve \"" << address << "\"!" << std::endl;
} else
socket = SDLNet_TCP_Open(&ip);
}
if(!socket && mode != Mode::Connect) {
// try hosting instead unless connecting was specified
if(SDLNet_ResolveHost(&ip, nullptr, port) == -1) {
// this can't fail
std::cerr << "Failed to resolve \"any\" address!" << std::endl;
} else
listen_socket = SDLNet_TCP_Open(&ip);
}
if(!socket && !listen_socket) {
std::cerr << "Failed to open socket: " << SDLNet_GetError() << std::endl;
return;
}
if(listen_socket) {
SDLNet_TCP_AddSocket(sock_set, listen_socket);
mode = Mode::Listen;
} else {
SDLNet_TCP_AddSocket(sock_set, socket);
mode = Mode::Connect;
}
}
void Multiplayer::disconnect() {
if(!socket)
return;
SDLNet_TCP_DelSocket(sock_set, socket);
SDLNet_TCP_Close(socket);
socket = nullptr;
handshake = false;
}
void Multiplayer::stop_listening() {
SDLNet_TCP_DelSocket(sock_set, listen_socket);
SDLNet_TCP_Close(listen_socket);
listen_socket = nullptr;
}