| #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; |
| } |