about summary refs log tree commit diff
path: root/src/engine/client/ec_srvbrowse.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/engine/client/ec_srvbrowse.cpp')
-rw-r--r--src/engine/client/ec_srvbrowse.cpp736
1 files changed, 736 insertions, 0 deletions
diff --git a/src/engine/client/ec_srvbrowse.cpp b/src/engine/client/ec_srvbrowse.cpp
new file mode 100644
index 00000000..1b04937a
--- /dev/null
+++ b/src/engine/client/ec_srvbrowse.cpp
@@ -0,0 +1,736 @@
+/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */
+#include <base/system.h>
+#include <engine/e_network.h>
+#include <engine/e_client_interface.h>
+#include <engine/e_config.h>
+#include <engine/e_memheap.h>
+#include <engine/e_engine.h>
+
+#include <mastersrv/mastersrv.h>
+
+#include <string.h>
+#include <stdlib.h>
+
+extern CNetClient m_NetClient;
+
+
+/* ------ 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 = (int *)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 = (SERVERENTRY **)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];
+		CNetChunk Packet;
+		int i;
+		
+		mem_copy(Buffer, SERVERBROWSE_GETINFO, sizeof(SERVERBROWSE_GETINFO));
+		Buffer[sizeof(SERVERBROWSE_GETINFO)] = current_token;
+			
+		Packet.m_ClientID = -1;
+		mem_zero(&Packet, sizeof(Packet));
+		Packet.m_Address.ip[0] = 255;
+		Packet.m_Address.ip[1] = 255;
+		Packet.m_Address.ip[2] = 255;
+		Packet.m_Address.ip[3] = 255;
+		Packet.m_Flags = NETSENDFLAG_CONNLESS;
+		Packet.m_DataSize = sizeof(Buffer);
+		Packet.m_pData = Buffer;
+		broadcast_time = time_get();
+
+		for(i = 8303; i <= 8310; i++)
+		{
+			Packet.m_Address.port = i;
+			m_NetClient.Send(&Packet);
+		}
+
+		if(config.debug)
+			dbg_msg("client", "broadcasting for servers");
+	}
+	else if(type == BROWSETYPE_INTERNET)
+		need_refresh = 1;
+	else if(type == BROWSETYPE_FAVORITES)
+	{
+		for(int 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];*/
+	CNetChunk Packet;
+
+	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;*/
+	
+	Packet.m_ClientID = -1;
+	Packet.m_Address = *addr;
+	Packet.m_Flags = NETSENDFLAG_CONNLESS;
+	/*p.data_size = sizeof(buffer);
+	p.data = buffer;
+	netclient_send(net, &p);*/
+
+	/* send old requtest style aswell */	
+	Packet.m_DataSize = sizeof(SERVERBROWSE_OLD_GETINFO);
+	Packet.m_pData = SERVERBROWSE_OLD_GETINFO;
+	m_NetClient.Send(&Packet);
+	
+	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;
+		CNetChunk Packet;
+		int i;
+		
+		need_refresh = 0;
+		
+		mem_zero(&Packet, sizeof(Packet));
+		Packet.m_ClientID = -1;
+		Packet.m_Flags = NETSENDFLAG_CONNLESS;
+		Packet.m_DataSize = sizeof(SERVERBROWSE_GETLIST);
+		Packet.m_pData = 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;
+			
+			Packet.m_Address = addr;
+			m_NetClient.Send(&Packet);
+		}
+
+		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();
+}