/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ #include #include #include #include #include #include #include #include #include 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(); }