#ifdef _WINDOWS
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#endif
#include <sstream>
#include <string>
#include <boost/cstdint.hpp>
#include <numeric>
#include <stdio.h>
#include <boost/asio.hpp>
#include <boost/regex.hpp>
#include <boost/shared_ptr.hpp>
#include "asserts.hpp"
#include "controls.hpp"
#include "formatter.hpp"
#include "level.hpp"
#include "multiplayer.hpp"
#include "preferences.hpp"
#include "random.hpp"
#include "regex_utils.hpp"
#include "unit_test.hpp"
using boost::asio::ip::tcp;
using boost::asio::ip::udp;
namespace multiplayer {
namespace {
boost::shared_ptr<boost::asio::io_service> asio_service;
boost::shared_ptr<tcp::socket> tcp_socket;
boost::shared_ptr<udp::socket> udp_socket;
boost::shared_ptr<udp::endpoint> udp_endpoint;
std::vector<boost::shared_ptr<udp::endpoint> > udp_endpoint_peers;
int32_t id;
int player_slot;
bool udp_packet_waiting()
{
if(!udp_socket) {
return false;
}
boost::asio::socket_base::bytes_readable command(true);
udp_socket->io_control(command);
return command.get() != 0;
}
bool tcp_packet_waiting()
{
if(!tcp_socket) {
return false;
}
boost::asio::socket_base::bytes_readable command(true);
tcp_socket->io_control(command);
return command.get() != 0;
}
}
int slot()
{
return player_slot;
}
manager::manager(bool activate)
{
if(activate) {
asio_service.reset(new boost::asio::io_service);
}
}
manager::~manager() {
udp_endpoint.reset();
tcp_socket.reset();
udp_socket.reset();
asio_service.reset();
player_slot = 0;
}
void setup_networked_game(const std::string& server)
{
boost::asio::io_service& io_service = *asio_service;
tcp::resolver resolver(io_service);
tcp::resolver::query query(server, "17002");
tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
tcp::resolver::iterator end;
tcp_socket.reset(new tcp::socket(io_service));
tcp::socket& socket = *tcp_socket;
boost::system::error_code error = boost::asio::error::host_not_found;
while(error && endpoint_iterator != end) {
socket.close();
socket.connect(*endpoint_iterator++, error);
}
if(error) {
fprintf(stderr, "NETWORK ERROR: Can't connect to host: %s\n", server.c_str());
throw multiplayer::error();
}
boost::array<char, 4> initial_response;
size_t len = socket.read_some(boost::asio::buffer(initial_response), error);
if(error) {
fprintf(stderr, "ERROR READING INITIAL RESPONSE\n");
throw multiplayer::error();
}
if(len != 4) {
fprintf(stderr, "INITIAL RESPONSE HAS THE WRONG SIZE: %d\n", (int)len);
throw multiplayer::error();
}
memcpy(&id, &initial_response[0], 4);
fprintf(stderr, "ID: %d\n", id);
udp::resolver udp_resolver(io_service);
udp::resolver::query udp_query(udp::v4(), server, "17001");
udp_endpoint.reset(new udp::endpoint);
*udp_endpoint = *udp_resolver.resolve(udp_query);
udp::endpoint& receiver_endpoint = *udp_endpoint;
udp_socket.reset(new udp::socket(io_service));
udp_socket->open(udp::v4());
boost::array<char, 4> udp_msg;
memcpy(&udp_msg[0], &id, 4);
// udp_socket->send_to(boost::asio::buffer(udp_msg), receiver_endpoint);
fprintf(stderr, "SENT UDP PACKET\n");
udp::endpoint sender_endpoint;
// len = udp_socket->receive_from(boost::asio::buffer(udp_msg), sender_endpoint);
fprintf(stderr, "GOT UDP PACKET: %d\n", (int)len);
std::string msg = "greetings!";
socket.write_some(boost::asio::buffer(msg), error);
if(error) {
fprintf(stderr, "NETWORK ERROR: Could not send data\n");
throw multiplayer::error();
}
}
namespace {
void send_confirm_packet(int nplayer, std::vector<char>& msg, bool has_confirm) {
boost::array<char, 4096> udp_msg;
msg.resize(6);
msg[0] = has_confirm ? 'a' : 'A';
memcpy(&msg[1], &id, 4);
msg.back() = player_slot;
if(nplayer == player_slot || nplayer < 0 || nplayer >= udp_endpoint_peers.size()) {
return;
}
std::cerr << "SENDING CONFIRM TO " << udp_endpoint_peers[nplayer]->port() << ": " << nplayer << "\n";
udp_socket->send_to(boost::asio::buffer(msg), *udp_endpoint_peers[nplayer]);
}
}
void sync_start_time(const level& lvl, boost::function<bool()> idle_fn)
{
if(!tcp_socket) {
return;
}
//find our host and port number within our NAT and tell the server
//about it, so if two servers from behind the same NAT connect to
//the server, it can tell them how to connect directly to each other.
std::string local_host;
int local_port = 0;
{
//send something on the UDP socket, just so that we get a port.
std::vector<char> send_buf;
send_buf.push_back('.');
udp_socket->send_to(boost::asio::buffer(send_buf), *udp_endpoint);
tcp::endpoint local_endpoint = tcp_socket->local_endpoint();
local_host = local_endpoint.address().to_string();
local_port = udp_socket->local_endpoint().port();
std::cerr << "LOCAL ENDPOINT: " << local_host << ":" << local_port << "\n";
}
std::ostringstream s;
s << "READY/" << lvl.id() << "/" << lvl.players().size() << "/" << local_host << " " << local_port;
boost::system::error_code error = boost::asio::error::host_not_found;
tcp_socket->write_some(boost::asio::buffer(s.str()), error);
if(error) {
fprintf(stderr, "ERROR WRITING TO SOCKET\n");
throw multiplayer::error();
}
while(!tcp_packet_waiting()) {
if(idle_fn) {
const bool res = idle_fn();
if(!res) {
std::cerr << "quitting game...\n";
throw multiplayer::error();
}
}
std::vector<char> send_buf(5);
send_buf[0] = 'Z';
memcpy(&send_buf[1], &id, 4);
udp_socket->send_to(boost::asio::buffer(send_buf), *udp_endpoint);
}
boost::array<char, 1024> response;
size_t len = tcp_socket->read_some(boost::asio::buffer(response), error);
if(error) {
fprintf(stderr, "ERROR READING FROM SOCKET\n");
throw multiplayer::error();
}
std::string str(&response[0], &response[0] + len);
if(std::string(str.begin(), str.begin() + 5) != "START") {
fprintf(stderr, "UNEXPECTED RESPONSE: '%s'\n", str.c_str());
throw multiplayer::error();
}
const char* ptr = str.c_str() + 6;
char* end_ptr = NULL;
const int nplayers = strtol(ptr, &end_ptr, 10);
ptr = end_ptr;
ASSERT_EQ(*ptr, '\n');
++ptr;
boost::asio::io_service& io_service = *asio_service;
udp::resolver udp_resolver(io_service);
udp_endpoint_peers.clear();
for(int n = 0; n != nplayers; ++n) {
const char* end = strchr(ptr, '\n');
ASSERT_LOG(end != NULL, "ERROR PARSING RESPONSE: " << str);
std::string line(ptr, end);
ptr = end+1;
if(line == "SLOT") {
player_slot = n;
udp_endpoint_peers.push_back(boost::shared_ptr<udp::endpoint>());
std::cerr << "SLOT: " << player_slot << "\n";
continue;
}
static boost::regex pattern("(.*?) (.*?)");
std::string host, port;
match_regex(line, pattern, &host, &port);
std::cerr << "SLOT " << n << " = " << host << " " << port << "\n";
udp_endpoint_peers.push_back(boost::shared_ptr<udp::endpoint>(new udp::endpoint));
udp::resolver::query peer_query(udp::v4(), host, port);
*udp_endpoint_peers.back() = *udp_resolver.resolve(peer_query);
if(preferences::relay_through_server()) {
*udp_endpoint_peers.back() = *udp_endpoint;
}
}
std::set<int> confirmed_players;
confirmed_players.insert(player_slot);
std::cerr << "PLAYER " << player_slot << " CONFIRMING...\n";
int confirmation_point = 1000;
for(int m = 0; m != 1000 && confirmed_players.size() < nplayers || m < confirmation_point + 50; ++m) {
std::vector<char> msg;
for(int n = 0; n != nplayers; ++n) {
send_confirm_packet(n, msg, confirmed_players.count(n) != 0);
}
while(udp_packet_waiting()) {
boost::array<char, 4096> udp_msg;
udp::endpoint endpoint;
size_t len = udp_socket->receive_from(boost::asio::buffer(udp_msg), endpoint);
if(len == 6 && ::toupper(udp_msg[0]) == 'A') {
confirmed_players.insert(udp_msg[5]);
if(udp_msg[5] >= 0 && udp_msg[5] < udp_endpoint_peers.size()) {
if(endpoint.port() != udp_endpoint_peers[udp_msg[5]]->port()) {
std::cerr << "REASSIGNING PORT " << endpoint.port() << " TO " << udp_endpoint_peers[udp_msg[5]]->port() << "\n";
}
*udp_endpoint_peers[udp_msg[5]] = endpoint;
}
if(confirmed_players.size() >= nplayers && m < confirmation_point) {
//now all players are confirmed
confirmation_point = m;
}
std::cerr << "CONFIRMED PLAYER: " << static_cast<int>(udp_msg[5]) << "/ " << player_slot << "\n";
}
}
//we haven't had any luck so far, so start port scanning, in case
//it's on another port.
if(m > 100 && (m%100) == 0) {
for(int n = 0; n != nplayers; ++n) {
if(n == player_slot || confirmed_players.count(n)) {
continue;
}
std::cerr << "PORTSCANNING FOR PORTS...\n";
for(int port_offset=-5; port_offset != 100; ++port_offset) {
udp::endpoint peer_endpoint;
const int port = udp_endpoint_peers[n]->port() + port_offset;
if(port <= 1024 || port >= 65536) {
continue;
}
std::string port_str = formatter() << port;
udp::resolver::query peer_query(udp::v4(), udp_endpoint_peers[n]->address().to_string(), port_str.c_str());
peer_endpoint = *udp_resolver.resolve(peer_query);
udp_socket->send_to(boost::asio::buffer(msg), peer_endpoint);
}
}
}
SDL_Delay(10);
}
if(confirmed_players.size() < nplayers) {
std::cerr << "COULD NOT CONFIRM NETWORK CONNECTION TO ALL PEERS\n";
throw multiplayer::error();
}
controls::set_delay(3);
std::cerr << "HANDSHAKING...\n";
if(player_slot == 0) {
int ping_id = 0;
std::map<int, int> ping_sent_at;
std::map<int, int> ping_player;
std::map<std::string, int> contents_ping;
std::map<int, int> player_nresponses;
std::map<int, int> player_latency;
int delay = 0;
int last_send = -1;
const int game_start = SDL_GetTicks() + 1000;
boost::array<char, 1024> receive_buf;
while(SDL_GetTicks() < game_start) {
const int ticks = SDL_GetTicks();
const int start_in = game_start - ticks;
if(start_in < 500 && delay == 0) {
//calculate what the delay should be
for(int n = 0; n != nplayers; ++n) {
if(n == player_slot) {
continue;
}
if(player_nresponses[n]) {
const int avg_latency = player_latency[n]/player_nresponses[n];
const int delay_time = avg_latency/(20*2) + 2;
if(delay_time > delay) {
delay = delay_time;
}
}
}
if(delay) {
std::cerr << "SET DELAY TO " << delay << "\n";
controls::set_delay(delay);
}
}
if(last_send == -1 || ticks >= last_send+10) {
last_send = ticks;
for(int n = 0; n != nplayers; ++n) {
if(n == player_slot) {
continue;
}
char buf[256];
int start_advisory = start_in;
const int player_responses = player_nresponses[n];
if(player_responses) {
const int avg_latency = player_latency[n]/player_responses;
start_advisory -= avg_latency/2;
if(start_advisory < 0) {
start_advisory = 0;
}
}
fprintf(stderr, "SENDING ADVISORY TO START IN %d - %d\n", start_in, (start_in - start_advisory));
const int buf_len = sprintf(buf, "PXXXX%d %d %d", ping_id, start_advisory, delay);
memcpy(&buf[1], &id, 4);
std::string msg(buf, buf + buf_len);
udp_socket->send_to(boost::asio::buffer(msg), *udp_endpoint_peers[n]);
ping_sent_at[ping_id] = ticks;
contents_ping[std::string(msg.begin()+5, msg.end())] = ping_id;
ping_player[ping_id] = n;
ping_id++;
}
}
while(udp_packet_waiting()) {
size_t len = udp_socket->receive(boost::asio::buffer(receive_buf));
if(len > 5 && receive_buf[0] == 'P') {
std::string msg(&receive_buf[0], &receive_buf[0] + len);
std::string msg_content(msg.begin()+5, msg.end());
ASSERT_LOG(contents_ping.count(msg_content), "UNRECOGNIZED PING: " << msg);
const int ping = contents_ping[msg_content];
const int latency = ticks - ping_sent_at[ping];
const int nplayer = ping_player[ping];
player_nresponses[nplayer]++;
player_latency[nplayer] += latency;
fprintf(stderr, "RECEIVED PING FROM %d IN %d AVG LATENCY %d\n", nplayer, latency, player_latency[nplayer]/player_nresponses[nplayer]);
} else {
if(len == 6 && receive_buf[0] == 'A') {
std::vector<char> msg;
const int player_num = receive_buf[5];
send_confirm_packet(player_num, msg, true);
}
}
}
SDL_Delay(1);
}
} else {
std::vector<int> start_time;
boost::array<char, 1024> buf;
for(;;) {
while(udp_packet_waiting()) {
size_t len = udp_socket->receive(boost::asio::buffer(buf));
std::cerr << "GOT MESSAGE: " << buf[0] << "\n";
if(len > 5 && buf[0] == 'P') {
memcpy(&buf[1], &id, 4); //write our ID for the return msg.
const std::string s(&buf[0], &buf[0] + len);
std::string::const_iterator begin_start_time = std::find(s.begin() + 5, s.end(), ' ');
ASSERT_LOG(begin_start_time != s.end(), "NO WHITE SPACE FOUND IN PING MESSAGE: " << s);
std::string::const_iterator begin_delay = std::find(begin_start_time + 1, s.end(), ' ');
ASSERT_LOG(begin_delay != s.end(), "NO WHITE SPACE FOUND IN PING MESSAGE: " << s);
const std::string start_in(begin_start_time+1, begin_delay);
const std::string delay_time(begin_delay+1, s.end());
const int start_in_num = atoi(start_in.c_str());
const int delay = atoi(delay_time.c_str());
start_time.push_back(SDL_GetTicks() + start_in_num);
while(start_time.size() > 5) {
start_time.erase(start_time.begin());
}
if(delay) {
std::cerr << "SET DELAY TO " << delay << "\n";
controls::set_delay(delay);
}
udp_socket->send_to(boost::asio::buffer(s), *udp_endpoint_peers[0]);
} else {
if(len == 6 && buf[0] == 'A') {
std::vector<char> msg;
const int player_num = buf[5];
send_confirm_packet(player_num, msg, true);
}
}
}
if(start_time.size() > 0) {
const int start_time_avg = std::accumulate(start_time.begin(), start_time.end(), 0)/start_time.size();
if(SDL_GetTicks() >= start_time_avg) {
break;
}
}
SDL_Delay(1);
}
}
rng::set_seed(0);
}
void send_and_receive()
{
if(!udp_socket || controls::num_players() == 1) {
return;
}
//send our ID followed by the send packet.
std::vector<char> send_buf(5);
send_buf[0] = 'C';
memcpy(&send_buf[1], &id, 4);
controls::write_control_packet(send_buf);
for(int n = 0; n != udp_endpoint_peers.size(); ++n) {
if(n == player_slot) {
continue;
}
udp_socket->send_to(boost::asio::buffer(send_buf), *udp_endpoint_peers[n]);
}
receive();
}
void receive()
{
while(udp_packet_waiting()) {
udp::endpoint sender_endpoint;
boost::array<char, 4096> udp_msg;
size_t len = udp_socket->receive(boost::asio::buffer(udp_msg));
if(len == 0 || udp_msg[0] != 'C') {
continue;
}
if(len < 5) {
fprintf(stderr, "UDP PACKET TOO SHORT: %d\n", (int)len);
continue;
}
controls::read_control_packet(&udp_msg[5], len - 5);
}
}
}
namespace {
struct Peer {
std::string host, port;
};
}
UTILITY(hole_punch_test) {
boost::asio::io_service io_service;
udp::resolver udp_resolver(io_service);
const char* server_hostname = "wesnoth.org";
const char* server_port = "17001";
udp::resolver::query udp_query(udp::v4(), server_hostname, server_port);
udp::endpoint udp_endpoint;
udp_endpoint = *udp_resolver.resolve(udp_query);
udp::socket udp_socket(io_service);
udp_socket.open(udp::v4());
udp_socket.send_to(boost::asio::buffer("hello"), udp_endpoint);
std::vector<Peer> peers;
boost::array<char, 1024> buf;
for(;;) {
udp_socket.receive(boost::asio::buffer(buf));
fprintf(stderr, "RECEIVED {{{%s}}}\n", &buf[0]);
char* beg = &buf[0];
char* mid = strchr(beg, ' ');
if(mid) {
*mid = 0;
const char* port = mid+1;
Peer peer;
peer.host = beg;
peer.port = port;
peers.push_back(peer);
}
for(int m = 0; m != 10; ++m) {
for(int n = 0; n != peers.size(); ++n) {
const std::string host = peers[n].host;
const std::string port = peers[n].port;
fprintf(stderr, "sending to %s %s\n", host.c_str(), port.c_str());
udp::resolver::query peer_query(udp::v4(), host, port);
udp::endpoint peer_endpoint;
peer_endpoint = *udp_resolver.resolve(peer_query);
udp_socket.send_to(boost::asio::buffer("peer"), peer_endpoint);
}
SDL_Delay(1000);
}
}
io_service.run();
}