/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */
#include
#include
#include
#include
#include
#include
#include
#include
#include
extern NETCLIENT *net;
/* ------ server browse ---- */
/* TODO: move all this to a separate file */
typedef struct SERVERENTRY_t SERVERENTRY;
struct SERVERENTRY_t
{
NETADDR addr;
int64 request_time;
int got_info;
SERVER_INFO info;
SERVERENTRY *next_ip; /* ip hashed list */
SERVERENTRY *prev_req; /* request list */
SERVERENTRY *next_req;
};
static HEAP *serverlist_heap = 0;
static SERVERENTRY **serverlist = 0;
static int *sorted_serverlist = 0;
enum
{
MAX_FAVORITES=256
};
static NETADDR favorite_servers[MAX_FAVORITES];
static int num_favorite_servers = 0;
static SERVERENTRY *serverlist_ip[256] = {0}; /* ip hash list */
static SERVERENTRY *first_req_server = 0; /* request list */
static SERVERENTRY *last_req_server = 0;
static int num_requests = 0;
static int need_refresh = 0;
static int num_sorted_servers = 0;
static int num_sorted_servers_capacity = 0;
static int num_servers = 0;
static int num_server_capacity = 0;
static int sorthash = 0;
static char filterstring[64] = {0};
static char filtergametypestring[128] = {0};
/* the token is to keep server refresh separated from each other */
static int current_token = 1;
static int serverlist_type = 0;
static int64 broadcast_time = 0;
int client_serverbrowse_lan() { return serverlist_type == BROWSETYPE_LAN; }
int client_serverbrowse_num() { return num_servers; }
SERVER_INFO *client_serverbrowse_get(int index)
{
if(index < 0 || index >= num_servers)
return 0;
return &serverlist[index]->info;
}
int client_serverbrowse_sorted_num() { return num_sorted_servers; }
SERVER_INFO *client_serverbrowse_sorted_get(int index)
{
if(index < 0 || index >= num_sorted_servers)
return 0;
return &serverlist[sorted_serverlist[index]]->info;
}
int client_serverbrowse_num_requests()
{
return num_requests;
}
static int client_serverbrowse_sort_compare_name(const void *ai, const void *bi)
{
SERVERENTRY *a = serverlist[*(const int*)ai];
SERVERENTRY *b = serverlist[*(const int*)bi];
return strcmp(a->info.name, b->info.name);
}
static int client_serverbrowse_sort_compare_map(const void *ai, const void *bi)
{
SERVERENTRY *a = serverlist[*(const int*)ai];
SERVERENTRY *b = serverlist[*(const int*)bi];
return strcmp(a->info.map, b->info.map);
}
static int client_serverbrowse_sort_compare_ping(const void *ai, const void *bi)
{
SERVERENTRY *a = serverlist[*(const int*)ai];
SERVERENTRY *b = serverlist[*(const int*)bi];
if(a->info.latency > b->info.latency) return 1;
if(a->info.latency < b->info.latency) return -1;
return 0;
}
static int client_serverbrowse_sort_compare_gametype(const void *ai, const void *bi)
{
SERVERENTRY *a = serverlist[*(const int*)ai];
SERVERENTRY *b = serverlist[*(const int*)bi];
return strcmp(a->info.gametype, b->info.gametype);
}
static int client_serverbrowse_sort_compare_progression(const void *ai, const void *bi)
{
SERVERENTRY *a = serverlist[*(const int*)ai];
SERVERENTRY *b = serverlist[*(const int*)bi];
if(a->info.progression > b->info.progression) return 1;
if(a->info.progression < b->info.progression) return -1;
return 0;
}
static int client_serverbrowse_sort_compare_numplayers(const void *ai, const void *bi)
{
SERVERENTRY *a = serverlist[*(const int*)ai];
SERVERENTRY *b = serverlist[*(const int*)bi];
if(a->info.num_players > b->info.num_players) return 1;
if(a->info.num_players < b->info.num_players) return -1;
return 0;
}
static void client_serverbrowse_filter()
{
int i = 0, p = 0;
num_sorted_servers = 0;
/* allocate the sorted list */
if(num_sorted_servers_capacity < num_servers)
{
if(sorted_serverlist)
mem_free(sorted_serverlist);
num_sorted_servers_capacity = num_servers;
sorted_serverlist = mem_alloc(num_sorted_servers_capacity*sizeof(int), 1);
}
/* filter the servers */
for(i = 0; i < num_servers; i++)
{
int filtered = 0;
if(config.b_filter_empty && serverlist[i]->info.num_players == 0)
filtered = 1;
else if(config.b_filter_full && serverlist[i]->info.num_players == serverlist[i]->info.max_players)
filtered = 1;
else if(config.b_filter_pw && serverlist[i]->info.flags&SRVFLAG_PASSWORD)
filtered = 1;
else if(config.b_filter_pure && (strcmp(serverlist[i]->info.gametype, "DM") != 0 && strcmp(serverlist[i]->info.gametype, "TDM") != 0 && strcmp(serverlist[i]->info.gametype, "CTF") != 0))
filtered = 1;
else if(config.b_filter_pure_map &&
!(strcmp(serverlist[i]->info.map, "dm1") == 0 ||
strcmp(serverlist[i]->info.map, "dm2") == 0 ||
strcmp(serverlist[i]->info.map, "dm6") == 0 ||
strcmp(serverlist[i]->info.map, "dm7") == 0 ||
strcmp(serverlist[i]->info.map, "dm8") == 0 ||
strcmp(serverlist[i]->info.map, "dm9") == 0 ||
strcmp(serverlist[i]->info.map, "ctf1") == 0 ||
strcmp(serverlist[i]->info.map, "ctf2") == 0 ||
strcmp(serverlist[i]->info.map, "ctf3") == 0 ||
strcmp(serverlist[i]->info.map, "ctf4") == 0 ||
strcmp(serverlist[i]->info.map, "ctf5") == 0)
)
{
filtered = 1;
}
else if(config.b_filter_ping < serverlist[i]->info.latency)
filtered = 1;
else if(config.b_filter_compatversion && strncmp(serverlist[i]->info.version, modc_net_version(), 3) != 0)
filtered = 1;
else
{
if(config.b_filter_string[0] != 0)
{
int matchfound = 0;
serverlist[i]->info.quicksearch_hit = 0;
/* match against server name */
if(str_find_nocase(serverlist[i]->info.name, config.b_filter_string))
{
matchfound = 1;
serverlist[i]->info.quicksearch_hit |= BROWSEQUICK_SERVERNAME;
}
/* match against players */
for(p = 0; p < serverlist[i]->info.num_players; p++)
{
if(str_find_nocase(serverlist[i]->info.players[p].name, config.b_filter_string))
{
matchfound = 1;
serverlist[i]->info.quicksearch_hit |= BROWSEQUICK_PLAYERNAME;
break;
}
}
/* match against map */
if(str_find_nocase(serverlist[i]->info.map, config.b_filter_string))
{
matchfound = 1;
serverlist[i]->info.quicksearch_hit |= BROWSEQUICK_MAPNAME;
}
if(!matchfound)
filtered = 1;
}
if(!filtered && config.b_filter_gametype[0] != 0)
{
/* match against game type */
if(!str_find_nocase(serverlist[i]->info.gametype, config.b_filter_gametype))
filtered = 1;
}
}
if(filtered == 0)
sorted_serverlist[num_sorted_servers++] = i;
}
}
static int client_serverbrowse_sorthash()
{
int i = config.b_sort&0xf;
i |= config.b_filter_empty<<4;
i |= config.b_filter_full<<5;
i |= config.b_filter_pw<<6;
i |= config.b_sort_order<<7;
i |= config.b_filter_compatversion<<8;
i |= config.b_filter_pure<<9;
i |= config.b_filter_pure_map<<10;
i |= config.b_filter_ping<<16;
return i;
}
static void client_serverbrowse_sort()
{
int i;
/* create filtered list */
client_serverbrowse_filter();
/* sort */
if(config.b_sort == BROWSESORT_NAME)
qsort(sorted_serverlist, num_sorted_servers, sizeof(int), client_serverbrowse_sort_compare_name);
else if(config.b_sort == BROWSESORT_PING)
qsort(sorted_serverlist, num_sorted_servers, sizeof(int), client_serverbrowse_sort_compare_ping);
else if(config.b_sort == BROWSESORT_MAP)
qsort(sorted_serverlist, num_sorted_servers, sizeof(int), client_serverbrowse_sort_compare_map);
else if(config.b_sort == BROWSESORT_NUMPLAYERS)
qsort(sorted_serverlist, num_sorted_servers, sizeof(int), client_serverbrowse_sort_compare_numplayers);
else if(config.b_sort == BROWSESORT_GAMETYPE)
qsort(sorted_serverlist, num_sorted_servers, sizeof(int), client_serverbrowse_sort_compare_gametype);
else if(config.b_sort == BROWSESORT_PROGRESSION)
qsort(sorted_serverlist, num_sorted_servers, sizeof(int), client_serverbrowse_sort_compare_progression);
/* invert the list if requested */
if(config.b_sort_order)
{
for(i = 0; i < num_sorted_servers/2; i++)
{
int temp = sorted_serverlist[i];
sorted_serverlist[i] = sorted_serverlist[num_sorted_servers-i-1];
sorted_serverlist[num_sorted_servers-i-1] = temp;
}
}
/* set indexes */
for(i = 0; i < num_sorted_servers; i++)
serverlist[sorted_serverlist[i]]->info.sorted_index = i;
str_copy(filtergametypestring, config.b_filter_gametype, sizeof(filtergametypestring));
str_copy(filterstring, config.b_filter_string, sizeof(filterstring));
sorthash = client_serverbrowse_sorthash();
}
static void client_serverbrowse_remove_request(SERVERENTRY *entry)
{
if(entry->prev_req || entry->next_req || first_req_server == entry)
{
if(entry->prev_req)
entry->prev_req->next_req = entry->next_req;
else
first_req_server = entry->next_req;
if(entry->next_req)
entry->next_req->prev_req = entry->prev_req;
else
last_req_server = entry->prev_req;
entry->prev_req = 0;
entry->next_req = 0;
num_requests--;
}
}
static SERVERENTRY *client_serverbrowse_find(NETADDR *addr)
{
SERVERENTRY *entry = serverlist_ip[addr->ip[0]];
for(; entry; entry = entry->next_ip)
{
if(net_addr_comp(&entry->addr, addr) == 0)
return entry;
}
return (SERVERENTRY*)0;
}
void client_serverbrowse_queuerequest(SERVERENTRY *entry)
{
/* add it to the list of servers that we should request info from */
entry->prev_req = last_req_server;
if(last_req_server)
last_req_server->next_req = entry;
else
first_req_server = entry;
last_req_server = entry;
num_requests++;
}
void client_serverbrowse_setinfo(SERVERENTRY *entry, SERVER_INFO *info)
{
int fav = entry->info.favorite;
entry->info = *info;
entry->info.favorite = fav;
entry->info.netaddr = entry->addr;
// all these are just for nice compability
if(entry->info.gametype[0] == '0' && entry->info.gametype[1] == 0)
str_copy(entry->info.gametype, "DM", sizeof(entry->info.gametype));
else if(entry->info.gametype[0] == '1' && entry->info.gametype[1] == 0)
str_copy(entry->info.gametype, "TDM", sizeof(entry->info.gametype));
else if(entry->info.gametype[0] == '2' && entry->info.gametype[1] == 0)
str_copy(entry->info.gametype, "CTF", sizeof(entry->info.gametype));
/*if(!request)
{
entry->info.latency = (time_get()-entry->request_time)*1000/time_freq();
client_serverbrowse_remove_request(entry);
}*/
entry->got_info = 1;
client_serverbrowse_sort();
}
SERVERENTRY *client_serverbrowse_add(NETADDR *addr)
{
int hash = addr->ip[0];
SERVERENTRY *entry = 0;
int i;
/* create new entry */
entry = (SERVERENTRY *)memheap_allocate(serverlist_heap, sizeof(SERVERENTRY));
mem_zero(entry, sizeof(SERVERENTRY));
/* set the info */
entry->addr = *addr;
entry->info.netaddr = *addr;
entry->info.latency = 999;
str_format(entry->info.address, sizeof(entry->info.address), "%d.%d.%d.%d:%d",
addr->ip[0], addr->ip[1], addr->ip[2],
addr->ip[3], addr->port);
str_format(entry->info.name, sizeof(entry->info.name), "\255%d.%d.%d.%d:%d", /* the \255 is to make sure that it's sorted last */
addr->ip[0], addr->ip[1], addr->ip[2],
addr->ip[3], addr->port);
/*if(serverlist_type == BROWSETYPE_LAN)
entry->info.latency = (time_get()-broadcast_time)*1000/time_freq();*/
/* check if it's a favorite */
for(i = 0; i < num_favorite_servers; i++)
{
if(net_addr_comp(addr, &favorite_servers[i]) == 0)
entry->info.favorite = 1;
}
/* add to the hash list */
entry->next_ip = serverlist_ip[hash];
serverlist_ip[hash] = entry;
if(num_servers == num_server_capacity)
{
SERVERENTRY **newlist;
num_server_capacity += 100;
newlist = mem_alloc(num_server_capacity*sizeof(SERVERENTRY*), 1);
mem_copy(newlist, serverlist, num_servers*sizeof(SERVERENTRY*));
mem_free(serverlist);
serverlist = newlist;
}
/* add to list */
serverlist[num_servers] = entry;
entry->info.server_index = num_servers;
num_servers++;
return entry;
}
void client_serverbrowse_set(NETADDR *addr, int type, int token, SERVER_INFO *info)
{
SERVERENTRY *entry = 0;
if(type == BROWSESET_MASTER_ADD)
{
if(serverlist_type != BROWSETYPE_INTERNET)
return;
if(!client_serverbrowse_find(addr))
{
entry = client_serverbrowse_add(addr);
client_serverbrowse_queuerequest(entry);
}
}
else if(type == BROWSESET_FAV_ADD)
{
if(serverlist_type != BROWSETYPE_FAVORITES)
return;
if(!client_serverbrowse_find(addr))
{
entry = client_serverbrowse_add(addr);
client_serverbrowse_queuerequest(entry);
}
}
else if(type == BROWSESET_TOKEN)
{
if(token != current_token)
return;
entry = client_serverbrowse_find(addr);
if(!entry)
entry = client_serverbrowse_add(addr);
if(entry)
{
client_serverbrowse_setinfo(entry, info);
if(serverlist_type == BROWSETYPE_LAN)
entry->info.latency = (time_get()-broadcast_time)*1000/time_freq();
else
entry->info.latency = (time_get()-entry->request_time)*1000/time_freq();
client_serverbrowse_remove_request(entry);
}
}
else if(type == BROWSESET_OLD_INTERNET)
{
entry = client_serverbrowse_find(addr);
if(entry)
{
client_serverbrowse_setinfo(entry, info);
if(serverlist_type == BROWSETYPE_LAN)
entry->info.latency = (time_get()-broadcast_time)*1000/time_freq();
else
entry->info.latency = (time_get()-entry->request_time)*1000/time_freq();
client_serverbrowse_remove_request(entry);
}
}
else if(type == BROWSESET_OLD_LAN)
{
entry = client_serverbrowse_find(addr);
if(entry)
if(!entry)
entry = client_serverbrowse_add(addr);
if(entry)
client_serverbrowse_setinfo(entry, info);
}
client_serverbrowse_sort();
}
void client_serverbrowse_refresh(int type)
{
/* clear out everything */
if(serverlist_heap)
memheap_destroy(serverlist_heap);
serverlist_heap = memheap_create();
num_servers = 0;
num_sorted_servers = 0;
mem_zero(serverlist_ip, sizeof(serverlist_ip));
first_req_server = 0;
last_req_server = 0;
num_requests = 0;
/* next token */
current_token = (current_token+1)&0xff;
/* */
serverlist_type = type;
if(type == BROWSETYPE_LAN)
{
unsigned char buffer[sizeof(SERVERBROWSE_GETINFO)+1];
NETCHUNK packet;
int i;
mem_copy(buffer, SERVERBROWSE_GETINFO, sizeof(SERVERBROWSE_GETINFO));
buffer[sizeof(SERVERBROWSE_GETINFO)] = current_token;
packet.client_id = -1;
mem_zero(&packet, sizeof(packet));
packet.address.ip[0] = 255;
packet.address.ip[1] = 255;
packet.address.ip[2] = 255;
packet.address.ip[3] = 255;
packet.flags = NETSENDFLAG_CONNLESS;
packet.data_size = sizeof(buffer);
packet.data = buffer;
broadcast_time = time_get();
for(i = 8303; i <= 8310; i++)
{
packet.address.port = i;
netclient_send(net, &packet);
}
if(config.debug)
dbg_msg("client", "broadcasting for servers");
}
else if(type == BROWSETYPE_INTERNET)
need_refresh = 1;
else if(type == BROWSETYPE_FAVORITES)
{
int i;
for(i = 0; i < num_favorite_servers; i++)
client_serverbrowse_set(&favorite_servers[i], BROWSESET_FAV_ADD, -1, 0);
}
}
static void client_serverbrowse_request_impl(NETADDR *addr, SERVERENTRY *entry)
{
/*unsigned char buffer[sizeof(SERVERBROWSE_GETINFO)+1];*/
NETCHUNK p;
if(config.debug)
{
dbg_msg("client", "requesting server info from %d.%d.%d.%d:%d",
addr->ip[0], addr->ip[1], addr->ip[2],
addr->ip[3], addr->port);
}
/*mem_copy(buffer, SERVERBROWSE_GETINFO, sizeof(SERVERBROWSE_GETINFO));
buffer[sizeof(SERVERBROWSE_GETINFO)] = current_token;*/
p.client_id = -1;
p.address = *addr;
p.flags = NETSENDFLAG_CONNLESS;
/*p.data_size = sizeof(buffer);
p.data = buffer;
netclient_send(net, &p);*/
/* send old requtest style aswell */
p.data_size = sizeof(SERVERBROWSE_OLD_GETINFO);
p.data = SERVERBROWSE_OLD_GETINFO;
netclient_send(net, &p);
if(entry)
entry->request_time = time_get();
}
void client_serverbrowse_request(NETADDR *addr)
{
client_serverbrowse_request_impl(addr, 0);
}
void client_serverbrowse_update()
{
int64 timeout = time_freq();
int64 now = time_get();
int count;
SERVERENTRY *entry, *next;
/* do server list requests */
if(need_refresh && !mastersrv_refreshing())
{
NETADDR addr;
NETCHUNK p;
int i;
need_refresh = 0;
mem_zero(&p, sizeof(p));
p.client_id = -1;
p.flags = NETSENDFLAG_CONNLESS;
p.data_size = sizeof(SERVERBROWSE_GETLIST);
p.data = SERVERBROWSE_GETLIST;
for(i = 0; i < MAX_MASTERSERVERS; i++)
{
addr = mastersrv_get(i);
if(!addr.ip[0] && !addr.ip[1] && !addr.ip[2] && !addr.ip[3])
continue;
p.address = addr;
netclient_send(net, &p);
}
if(config.debug)
dbg_msg("client", "requesting server list");
}
/* do timeouts */
entry = first_req_server;
while(1)
{
if(!entry) /* no more entries */
break;
next = entry->next_req;
if(entry->request_time && entry->request_time+timeout < now)
{
/* timeout */
client_serverbrowse_remove_request(entry);
num_requests--;
}
entry = next;
}
/* do timeouts */
entry = first_req_server;
count = 0;
while(1)
{
if(!entry) /* no more entries */
break;
/* no more then 10 concurrent requests */
if(count == config.b_max_requests)
break;
if(entry->request_time == 0)
client_serverbrowse_request_impl(&entry->addr, entry);
count++;
entry = entry->next_req;
}
/* check if we need to resort */
/* TODO: remove the strcmp */
if(sorthash != client_serverbrowse_sorthash() || strcmp(filterstring, config.b_filter_string) != 0 || strcmp(filtergametypestring, config.b_filter_gametype) != 0)
client_serverbrowse_sort();
}
int client_serverbrowse_isfavorite(NETADDR addr)
{
/* search for the address */
int i;
for(i = 0; i < num_favorite_servers; i++)
{
if(net_addr_comp(&addr, &favorite_servers[i]) == 0)
return 1;
}
return 0;
}
void client_serverbrowse_addfavorite(NETADDR addr)
{
int i;
SERVERENTRY *entry;
if(num_favorite_servers == MAX_FAVORITES)
return;
/* make sure that we don't already have the server in our list */
for(i = 0; i < num_favorite_servers; i++)
{
if(net_addr_comp(&addr, &favorite_servers[i]) == 0)
return;
}
/* add the server to the list */
favorite_servers[num_favorite_servers++] = addr;
entry = client_serverbrowse_find(&addr);
if(entry)
entry->info.favorite = 1;
dbg_msg("", "added fav, %p", entry);
}
void client_serverbrowse_removefavorite(NETADDR addr)
{
int i;
SERVERENTRY *entry;
for(i = 0; i < num_favorite_servers; i++)
{
if(net_addr_comp(&addr, &favorite_servers[i]) == 0)
{
mem_move(&favorite_servers[i], &favorite_servers[i+1], num_favorite_servers-(i+1));
num_favorite_servers--;
entry = client_serverbrowse_find(&addr);
if(entry)
entry->info.favorite = 0;
return;
}
}
}
void client_serverbrowse_save()
{
int i;
char addrstr[128];
char buffer[256];
for(i = 0; i < num_favorite_servers; i++)
{
net_addr_str(&favorite_servers[i], addrstr, sizeof(addrstr));
str_format(buffer, sizeof(buffer), "add_favorite %s", addrstr);
engine_config_write_line(buffer);
}
}
int client_serverbrowse_refreshingmasters()
{
return mastersrv_refreshing();
}