+// HostCache interface
+hostcache_mask_t hostcache_andmasks[HOSTCACHE_ANDMASKCOUNT];
+hostcache_mask_t hostcache_ormasks[HOSTCACHE_ORMASKCOUNT];
+
+hostcache_infofield_t hostcache_sortbyfield;
+qboolean hostcache_sortdescending;
+
+int hostcache_viewcount = 0;
+hostcache_t *hostcache_viewset[HOSTCACHE_VIEWCACHESIZE];
+
+int hostcache_cachecount;
+hostcache_t hostcache_cache[HOSTCACHE_TOTALSIZE];
+
+qboolean hostcache_consoleoutput;
+
+// helper function to insert a value into the viewset
+// spare entries will be removed
+static void _HostCache_ViewSet_InsertBefore( int index, hostcache_t *entry )
+{
+ int i;
+ if( ++hostcache_viewcount > HOSTCACHE_VIEWCACHESIZE )
+ hostcache_viewcount = HOSTCACHE_VIEWCACHESIZE;
+ for( i = hostcache_viewcount - 1; i > index; i-- )
+ hostcache_viewset[i] = hostcache_viewset[i - 1];
+ hostcache_viewset[index] = entry;
+}
+
+// we suppose hostcache_viewcount to be valid, ie > 0
+static void _HostCache_ViewSet_Remove( int index )
+{
+ for( --hostcache_viewcount; index < hostcache_viewcount; index++ )
+ hostcache_viewset[index] = hostcache_viewset[index + 1];
+}
+
+// returns true if A should be inserted before B
+static qboolean _HostCache_SortTest( hostcache_t *A, hostcache_t *B )
+{
+ int result = 0; // > 0 if for numbers A > B and for text if A < B
+
+ if( hostcache_sortbyfield == HCIF_PING )
+ result = A->info.ping - B->info.ping;
+ else if( hostcache_sortbyfield == HCIF_MAXPLAYERS )
+ result = A->info.maxplayers - B->info.maxplayers;
+ else if( hostcache_sortbyfield == HCIF_NUMPLAYERS )
+ result = A->info.numplayers - B->info.numplayers;
+ else if( hostcache_sortbyfield == HCIF_PROTOCOL )
+ result = A->info.protocol - B->info.protocol;
+ else if( hostcache_sortbyfield == HCIF_CNAME )
+ result = strcmp( B->info.cname, A->info.cname );
+ else if( hostcache_sortbyfield == HCIF_GAME )
+ result = strcmp( B->info.game, A->info.game );
+ else if( hostcache_sortbyfield == HCIF_MAP )
+ result = strcmp( B->info.map, A->info.map );
+ else if( hostcache_sortbyfield == HCIF_MOD )
+ result = strcmp( B->info.mod, A->info.mod );
+ else if( hostcache_sortbyfield == HCIF_NAME )
+ result = strcmp( B->info.name, A->info.name );
+
+ if( hostcache_sortdescending )
+ return result > 0;
+ return result < 0;
+}
+
+static qboolean _hc_testint( int A, hostcache_maskop_t op, int B )
+{
+ if( op == HCMO_LESS )
+ return A < B;
+ else if( op == HCMO_LESSEQUAL )
+ return A <= B;
+ else if( op == HCMO_EQUAL )
+ return A == B;
+ else if( op == HCMO_GREATER )
+ return A > B;
+ else if( op == HCMO_NOTEQUAL )
+ return A != B;
+ else // HCMO_GREATEREQUAL
+ return A >= B;
+}
+
+static qboolean _hc_teststr( const char *A, hostcache_maskop_t op, const char *B )
+{
+ if( op == HCMO_CONTAINS ) // A info B mask
+ return *B && !!strstr( A, B ); // we want a real bool
+ else if( op == HCMO_NOTCONTAIN )
+ return !*B || !strstr( A, B );
+ else if( op == HCMO_LESS )
+ return strcmp( A, B ) < 0;
+ else if( op == HCMO_LESSEQUAL )
+ return strcmp( A, B ) <= 0;
+ else if( op == HCMO_EQUAL )
+ return strcmp( A, B ) == 0;
+ else if( op == HCMO_GREATER )
+ return strcmp( A, B ) > 0;
+ else if( op == HCMO_NOTEQUAL )
+ return strcmp( A, B ) != 0;
+ else // HCMO_GREATEREQUAL
+ return strcmp( A, B ) >= 0;
+}
+
+static qboolean _HostCache_TestMask( hostcache_mask_t *mask, hostcache_info_t *info )
+{
+ if( !_hc_testint( info->ping, mask->tests[HCIF_PING], mask->info.ping ) )
+ return false;
+ if( !_hc_testint( info->maxplayers, mask->tests[HCIF_MAXPLAYERS], mask->info.maxplayers ) )
+ return false;
+ if( !_hc_testint( info->numplayers, mask->tests[HCIF_NUMPLAYERS], mask->info.numplayers ) )
+ return false;
+ if( !_hc_testint( info->protocol, mask->tests[HCIF_PROTOCOL], mask->info.protocol ))
+ return false;
+ if( *mask->info.cname
+ && !_hc_teststr( info->cname, mask->tests[HCIF_CNAME], mask->info.cname ) )
+ return false;
+ if( *mask->info.game
+ && !_hc_teststr( info->game, mask->tests[HCIF_GAME], mask->info.game ) )
+ return false;
+ if( *mask->info.mod
+ && !_hc_teststr( info->mod, mask->tests[HCIF_MOD], mask->info.mod ) )
+ return false;
+ if( *mask->info.map
+ && !_hc_teststr( info->map, mask->tests[HCIF_MAP], mask->info.map ) )
+ return false;
+ if( *mask->info.name
+ && !_hc_teststr( info->name, mask->tests[HCIF_NAME], mask->info.name ) )
+ return false;
+ return true;
+}
+
+static void _HostCache_Insert( hostcache_t *entry )
+{
+ int start, end, mid;
+ if( hostcache_viewcount == HOSTCACHE_VIEWCACHESIZE )
+ return;
+ // now check whether it passes through the masks mask
+ for( start = 0 ; hostcache_andmasks[start].active && start < HOSTCACHE_ANDMASKCOUNT ; start++ )
+ if( !_HostCache_TestMask( &hostcache_andmasks[start], &entry->info ) )
+ return;
+
+ for( start = 0 ; hostcache_ormasks[start].active && start < HOSTCACHE_ORMASKCOUNT ; start++ )
+ if( _HostCache_TestMask( &hostcache_ormasks[start], &entry->info ) )
+ break;
+ if( start == HOSTCACHE_ORMASKCOUNT || (start > 0 && !hostcache_ormasks[start].active) )
+ return;
+
+ if( !hostcache_viewcount ) {
+ _HostCache_ViewSet_InsertBefore( 0, entry );
+ return;
+ }
+ // ok, insert it, we just need to find out where exactly:
+
+ // two special cases
+ // check whether to insert it as new first item
+ if( _HostCache_SortTest( entry, hostcache_viewset[0] ) ) {
+ _HostCache_ViewSet_InsertBefore( 0, entry );
+ return;
+ } // check whether to insert it as new last item
+ else if( !_HostCache_SortTest( entry, hostcache_viewset[hostcache_viewcount - 1] ) ) {
+ _HostCache_ViewSet_InsertBefore( hostcache_viewcount, entry );
+ return;
+ }
+ start = 0;
+ end = hostcache_viewcount - 1;
+ while( end > start + 1 )
+ {
+ mid = (start + end) / 2;
+ // test the item that lies in the middle between start and end
+ if( _HostCache_SortTest( entry, hostcache_viewset[mid] ) )
+ // the item has to be in the upper half
+ end = mid;
+ else
+ // the item has to be in the lower half
+ start = mid;
+ }
+ _HostCache_ViewSet_InsertBefore( start + 1, entry );
+}
+
+static void _HostCache_Remove( hostcache_t *entry )
+{
+ int i;
+ for( i = 0; i < hostcache_viewcount; i++ )
+ {
+ if (hostcache_viewset[i] == entry)
+ {
+ _HostCache_ViewSet_Remove(i);
+ break;
+ }
+ }
+}
+
+void HostCache_RebuildViewSet(void)
+{
+ int i;
+
+ hostcache_viewcount = 0;
+ for( i = 0 ; i < hostcache_cachecount ; i++ )
+ if( hostcache_cache[i].finished )
+ _HostCache_Insert( &hostcache_cache[i] );
+}
+
+void HostCache_ResetMasks(void)
+{
+ memset( &hostcache_andmasks, 0, sizeof( hostcache_andmasks ) );
+ memset( &hostcache_ormasks, 0, sizeof( hostcache_ormasks ) );
+}
+
+#if 0
+static void _HostCache_Test(void)
+{
+ int i;
+ for( i = 0 ; i < 1024 ; i++ ) {
+ memset( &hostcache_cache[hostcache_cachecount], 0, sizeof( hostcache_t ) );
+ hostcache_cache[hostcache_cachecount].info.ping = rand() % 450 + 250;
+ dpsnprintf( hostcache_cache[hostcache_cachecount].info.name, 128, "Black's HostCache Test %i", i );
+ hostcache_cache[hostcache_cachecount].finished = true;
+ sprintf( hostcache_cache[hostcache_cachecount].line1, "%i %s", hostcache_cache[hostcache_cachecount].info.ping, hostcache_cache[hostcache_cachecount].info.name );
+ _HostCache_Insert( &hostcache_cache[hostcache_cachecount] );
+ hostcache_cachecount++;
+ }
+}
+#endif
+
+void HostCache_QueryList(void)
+{
+ masterquerytime = realtime;
+ masterquerycount = 0;
+ masterreplycount = 0;
+ serverquerycount = 0;
+ serverreplycount = 0;
+ hostcache_cachecount = 0;
+ hostcache_viewcount = 0;
+ hostcache_consoleoutput = false;
+ NetConn_QueryMasters();
+
+ //_HostCache_Test();
+}
+
+// rest
+