//
//============================================================
//
// Contents:  Network routines
//
// Owner   :  AndyG
// Created :  Aug 9 1999
//
// All Rights reserved worldwide (c) Microsoft
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL
//============================================================
//
#include "pch.hpp"
#include "Net_Header.hpp"

#include <ZoneMatch.h>
#include <ZoneAsync.h>
#include "GunGameList.h"
bool ZoneMatchConnected=FALSE;

#define FREE_PTR(ptr) if ((void *)ptr) { free((void *)ptr); (ptr)=NULL; }

const char GUN_ROWID_STR[] = "Row ID";

// mdm - this is the id that GameOS checks when decided whether or not a SPEW should be 
// executed.
const char GUN_SPEW_ID[] = "GUN";

// If there are fewer than this number of games up on the server, then GUN
// will always do an update instead of a refresh.
const int MIN_SERVER_LIST_SIZE_FOR_REFRESH = 20;

// This number represents the maximum percentage of the total list of games that
// a refresh is allowed to be used for.  For example, if a refresh would
// result in 90% of the total list being refreshed, then an update would be sent
// instead.  This constant just defines what that maximum threshold is.
const float MAX_REFRESH_LIST_PERCENTAGE = .6f; // mdm - 60% or less of total list we will refresh

// This represents the maximum allowed string length for the row filter of
// a refresh view.  If the rowfilter would exceed this length, the game instead
// requests a full update.
const int MAX_REFRESH_FILTER_LENGTH = 2048;//mdm - ensure refresh is never chosen -- later will set to 2048 when ready to enable

// Number of seconds to wait for further activity before considering a query dead
const double QUERY_TIMEOUT = 5.0;


// mlyons convenience functions for _mb funcs
__forceinline int _mbscmp(const char *s1, const char *s2)
{
	return _mbscmp((const unsigned char *)s1, (const unsigned char *)s2);
}

__forceinline char * _mbscpy(char *s1, const char *s2)
{
	return (char *)_mbscpy((unsigned char *)s1, (const unsigned char *)s2);
}

__forceinline int _mbsicmp(const char *s1, const char *s2)
{
	return _mbsicmp((const unsigned char *)s1, (const unsigned char *)s2);
}

__forceinline size_t _mbslen(const char *s1)
{
	return _mbslen((const unsigned char *)s1);
}

__forceinline char * _mbsncat(char *s1, const char *s2, size_t t)
{
	return (char *)_mbsncat((unsigned char *)s1, (const unsigned char *)s2, t);
}

__forceinline char * _mbsncpy(char *s1, const char *s2, size_t t)
{
	return (char *)_mbsncpy((unsigned char *)s1, (const unsigned char *)s2, t);
}


struct GUNQuery;

//========================================================================
//========================================================================
class GUNServerBrowser : public ServerBrowser
{
	friend bool __stdcall InitGUNServerBrowser(void);

private:
	// general use
	virtual	bool __stdcall Initialize(void);
	virtual	bool __stdcall Disconnect(void);
	virtual int __stdcall GetStatus(void);
	virtual bool __stdcall Synchronize(void);
	virtual const char *__stdcall GetDescription(void);
	virtual bool __stdcall Release(void);

	// for browsers
    virtual bool __stdcall PrepareJoinGame(const char * szGameName, void **ppDPlay);

	virtual bool __stdcall PrepareRefresh(void);
	virtual bool __stdcall Refresh(char *gameID);
	virtual bool __stdcall RefreshServerInfo(char *gameID);

	virtual bool __stdcall CancelActivity(void); // cancel current update if there is one
	virtual bool __stdcall StartUpdate(void); // start a new update.  
	virtual bool __stdcall GetNetStatus(void);

	static int __stdcall EnumSessionsCallback( LPCDPSESSIONDESC2 lpSessionDesc, LPDWORD lpdwTimeOut, DWORD dwFlags, void* lpContext );

    void CompressRowFilter( char * rowFilter );
    void CheckQueries( GUNQuery * pQuery );

public:
	static GUNServerBrowser * m_pGun;
	bool						m_bManualDisconnect;
};

GUNServerBrowser * GUNServerBrowser::m_pGun = NULL;


//========================================================================
//========================================================================
class GUNServerAdvertiser : public ServerAdvertiser
{
	friend bool __stdcall InitGUNServerAdvertiser(void);

    enum {
        MAX_REGISTER_TRIES = 7,
		ADVERTISE_CHECK_FREQUENCY = 30, // default frequency of checks to make sure game is advertising properly
		ADVERTISE_FREQUENCY_INCREMENT = 10, // increment to check frequency when advertise attempt fails
        MAX_PLAYER_NAME = 255
    };

        
private:
    // types

    // used in callbacks to store to and from data needed during processing
    struct CBData
    {
        IZoneMatchApplication * pDestApp;
        IZoneMatchApplication * pFromApp;

        CBData()
        {
            pDestApp = NULL; 
            pFromApp = NULL;
        }
    };

	// general use
	virtual	bool __stdcall Initialize(void);
	virtual	bool __stdcall Disconnect(void);
	virtual int __stdcall GetStatus(void);
	virtual bool __stdcall Synchronize(void);
	virtual const char *__stdcall GetDescription(void);
	virtual bool __stdcall Release(void);


	// for servers
	virtual bool __stdcall CreateGame(const char *szGameName, const char *szPlayerName, int MaxPlayers, const char *szPassword, const GUID &guidInstance, DWORD dwFlags);
	virtual bool __stdcall CreatePlayer(DWORD dwItemID, const char *szPlayerName, bool bBot);
	virtual bool __stdcall RemovePlayer(DWORD dwItemID, const char *szPlayerName, bool bBot);
	virtual bool __stdcall SetItemValue(DWORD dwPlayer, const char * szName , const char * szValue);
	virtual bool __stdcall SetFlags(DWORD dwFlags);

    // for moving values between one reg attempt and the next
    static BOOL WINAPI AppEnumItemsCB( DWORD dwItemID,
                                       LPCSTR pszKey,
                                       LPCSTR pszValue,
                                       LPVOID pContext );
    static BOOL WINAPI AppEnumPlayersCB( DWORD dwItemID,
                                         LPCSTR pszPlayerName,
                                         LPVOID pContext );
    void PushAdvertItems( IZoneMatchApplication * pDestApp, IZoneMatchApplication * pFromApp );


	static int __stdcall EnumSessionsCallback( LPCDPSESSIONDESC2 lpSessionDesc, LPDWORD lpdwTimeOut, DWORD dwFlags, void* lpContext );

    // internal methods
    bool RegisterGame( IZoneMatchApplication::APPDESCRIPTION * pAppDesc );
    void FreeInterfaces( void );

    // data
    unsigned short                          m_AppQueryPort;
    DWORD                                   m_dwFlags;
    char                                    m_PlayerName[MAX_PLAYER_NAME+1];
	IZoneMatchApplication::APPDESCRIPTION * m_pAppDesc;
	double									m_LastAdvertiseTime;
	double									m_AdvertiseCheckFreq;
    bool                                    m_bInReregister;
    char                                    m_szFlags[32]; // stores dplay game flags (pprotected, locked, etc..)
};




enum GUN_QUERY_STYLE
{
    GQS_ONESHOT, // full update once then stop
    GQS_UPDATING // full update followed by continuing change updates.
};

enum GUN_QUERY_TYPE
{
    GQT_SERVER,
    GQT_PLAYER
};


// 
// Represents a GUN query.  Used as container for all information
// needed when processing a query for servers or players.
//
struct GUNQuery
{
    GUN_QUERY_TYPE          queryType;  // type of query we want, player or server
    GUN_QUERY_STYLE         queryStyle; // type of query we want - continuous or oneshot
    IZoneMatchTableView *   pZMView;    // view we are using
    IZoneRowManager *       pRowMgr;    // row manager of the view
    LPZONEMATCHROW          pHeaders;   // pointer to header store managed by the row manager of the view
    bool                    bActive;    // true if query is currently running
    char *		            pFilter;    // row filter key to use for query : Format is key=val, e.g. name=fred
	char *					pGameID;	// if non-null, indicates which game server this query was for (used for caching purposes)
	int                     tableSize;	// number of rows in the table we are downloading, -1 if we don't know or invalid
	int						progressCount;	// current row we are downloading, -1 if we don't know or invalid
    double                  startTime;  // time the query was initiated
    double                  lastUpdateTime; // time the query last received an update
    bool                    bIsServerInfo;   // true if this is a serverinfo query
    bool                    bIsFullUpdate;     // true if this is a full update query
    GUNQuery *              pNext;      // pointer to next query in the list
	

    GUNQuery()
    {
        pZMView = NULL;
        pRowMgr = NULL;
        pHeaders = NULL;
        queryStyle = GQS_ONESHOT;
        queryType = GQT_SERVER;
        bActive = false;
        pFilter = NULL;
		pGameID = NULL;
		tableSize = -1;
		progressCount = -1;
        startTime = 0.0;
        lastUpdateTime = 0.0;
        bIsServerInfo = false;
        bIsFullUpdate = false;
        pNext = NULL;
    }

	// These are needed to avoid GOS memory management kicking in and ruining our lives.
    void * operator new(size_t size) { return malloc(size); }
    void * operator new[](size_t size) { return malloc(size); }
    void operator delete( void* ptr ) { free(ptr); }
    void operator delete[] ( void* ptr ) { free(ptr); }
};

// exported via Network.hpp
// see GUN_REGISTRATION_STATUS for values
int GUNRegStatus = GRS_UNINITIALIZED;

// exported via Network.hpp
// see GUN_STATUS for values
int GUNStatus=GS_DISCONNECTED;

// exported via Network.hpp 
// see GUN_NET_STATUS for values
int GUNNetStatus = ServerBrowser::GNS_NONE;

// error value for the last problem
// that occurred relating to ZoneMatch.
HRESULT GUNLastError = S_OK;

// main browser object
IZoneMatchBrowser* g_pZMBrowser=NULL;
// async object used to control activity of main browser
IZoneAsync* g_pZMAsync=NULL;

// objects related to starting and running a game.
IZoneAsync* g_pZMAsyncApp=NULL;

IZoneMatchApplication*  g_pZMApp=NULL;
IZoneMatchRegistration* g_pZMReg=NULL;

// mdm - remember the size of the last full update that we did.
int g_ServerListSize = -1;


// indicates if player data should be downloaded with games
bool     g_bDownloadPlayers = false;

// indicates if we are expected to fire off an update when GUN is up & running
bool     g_UpdatePending = false;

// indicates if we are expected to fire off a refresh when GUN is up & running
bool     g_RefreshPending = false;

// Server query objects
GUNQuery g_serverQuery;

// Player query objects
GUNQuery g_playerQuery;

const float GUN_REFRESH_DELAY = 2.0f; // num seconds between server list refresh


/////////////// forward declarations /////////////////////

int  GUNSynchronize( bool bStartQueries = true );

void
GUNUpdateTable( Browse::TableList * table,
                const char * rowIDStr,
                LPZONEMATCHROW pKeys,
                LPZONEMATCHROW pValues );

void GUNCloseAllQueries( void );

bool
GUNStartQuery( GUNQuery * query );

void
GUNUpdateServerCB(
		DWORD iRowID,
        IZoneMatchTableView::UPDATE_STATUS usRow,
        LPZONEMATCHROW pRow,
        LPVOID pContext );

void
GUNUpdatePlayerCB(
		DWORD iRowID,
        IZoneMatchTableView::UPDATE_STATUS usRow,
        LPZONEMATCHROW pRow,
        LPVOID pContext );


void GUNCloseQuery( GUNQuery * query, bool bIsError = false );
void CleanUpRefresh( GUNQuery * pQuery );
void QueryMarkRefresh( GUNQuery * pQuery, const char * gameID );
void QueryMarkAllRefresh( GUNQuery * pQuery );

void
CloseGUN( void );

int
GUNUpdateServer( void );

bool GUNRegisterDLL( LPCTSTR dllPath );

bool GUNRegisterDLLs( void );

void
PushGameList( void );

void
GUNFreeGOSListOfGames( ListOfGames * gameList );

bool
GUNPrepareDPlay( const char * GameName );

int
GetGUNStatus( void );

int
GetGUNRegStatus( void );

int
GetGUNNetStatus( void );

int 
GetGUNLastError( void );

void
GetGUNDownloadStats( int * tableSize, int * progressCount );

void DiscoverZoneMatchSettings( void );
void PushGameToGOS( const char * gameID );

/////////////// end forward declarations ////////////////


////////////////////////////////////////////////////////////////////////////////////////
//  GetGUNStatus()
//  Purpose:
//       Returns the status of the ZoneMatch system.  This describes either the current
//       phase that the ZoneMatch connection is in during initialization (culminating in
//       GS_CONNECTED, which indicates a successfully initialized system).  If an error
//       occurs during a phase of the initialization, the GUNStatus is set to the
//       negative value of the current phase (thus all errors result in GUNStatus values
//       that are less than zero) and the GUNLastError value is set to hold the hresult
//       that signaled the failure (see GetGUNLastError).
//  
//  Parameters:
//       [void] :
//  
//  Returns:  int __stdcall
//  
//  Change Log
//       Author                 MMDDYY  Description
//       --------------------   ------  ------------------------------------------------
//       Michael Moore          090600  Initial Version
//  
//       
////////////////////////////////////////////////////////////////////////////////////////
int __stdcall GetGUNStatus( void )
{
    return GUNStatus;
}

////////////////////////////////////////////////////////////////////////////////////////
//  GetGUNRegStatus()
//  Purpose:
//       Returns the current registration status.  See the GUN_REGISTRATION_STATUS 
//       enum in GUNGameList.h for further info on the range of status values.
//  
//  Parameters:
//       [void] :
//  
//  Returns:  int __stdcall
//  
//  Change Log
//       Author                 MMDDYY  Description
//       --------------------   ------  ------------------------------------------------
//       Michael Moore          091800  Initial Version
//  
//       
////////////////////////////////////////////////////////////////////////////////////////
int __stdcall GetGUNRegStatus( void )
{
	return GUNRegStatus;
}

////////////////////////////////////////////////////////////////////////////////////////
//  GetGUNNetStatus()
//  Purpose:
//       Returns the status of the network activity for ZoneMatch browsing.  This is 
//       either GNS_NONE, GNS_DOWNLOADING_PLAYERS, GNS_DOWNLOADING_SERVERS, or a 
//       combination of them.
//  
//  Parameters:
//       [void] :
//  
//  Returns:  int __stdcall
//  
//  Change Log
//       Author                 MMDDYY  Description
//       --------------------   ------  ------------------------------------------------
//       Michael Moore          090600  Initial Version
//  
//       
////////////////////////////////////////////////////////////////////////////////////////
int __stdcall GetGUNNetStatus( void )
{
    return GUNNetStatus;
}

////////////////////////////////////////////////////////////////////////////////////////
//  GetGUNLastError()
//  Purpose:
//       Returns the HRESULT of the last error encountered using ZoneMatch.
//  
//  Parameters:
//       [void] :
//  
//  Returns:  int __stdcall
//  
//  Change Log
//       Author                 MMDDYY  Description
//       --------------------   ------  ------------------------------------------------
//       Michael Moore          090600  Initial Version
//  
//       
////////////////////////////////////////////////////////////////////////////////////////
int __stdcall GetGUNLastError( void )
{
	return GUNLastError;
}



const char * GetGUNErrorMessage( void )
{
	const char * errorMsg;

	switch ( GUNLastError )
	{
	case ZT_E_VERSION_NOT_AVAIL:	
		errorMsg = "The requested component version in not available.";
		break;
	case ZT_E_BUFFER_TOO_SMALL:
		errorMsg = "The provided buffer is too small to complete the operation.";
		break;
	case ZT_E_INTERNAL:
		errorMsg = "An internal error occurred.";
		break;
	case ZT_E_INVALID_STATUS:
		errorMsg = "The ZoneTech object status is not in an expected state.";
		break;
	case ZT_E_STR_TOO_LONG:
		errorMsg = "The string exceeds the maximum supported string length for this operation.";
		break;
	case ZT_E_PENDING:
		errorMsg = "The operation is pending.";
		break;
	case ZT_E_ATTR_NOT_FOUND:
		errorMsg = "The Attribute was not found.";
		break;
	case ZT_E_NOT_INITED:
		errorMsg = "The Init method for this object must be called before this operation.";
		break;
	case ZT_E_ALREADY_INITED:
		errorMsg = "The Init method cannot be called more than once on this object.";
		break;
	case ZT_E_WRONG_THREAD:
		errorMsg = "Call to object was made on the wrong thread.";
		break;
	case ZONE_E_ASSERT:
		errorMsg = "Zone Error Assertion.";
		break;
	/////////////////////////////////////////////////////////////////////////
	// ZoneTech Network Error Codes
	/////////////////////////////////////////////////////////////////////////
	case ZT_E_CANNOT_INIT_WINSOCK: 
		errorMsg = "Failed to initialize the Windows Sockets library.";
		break;
	case ZT_E_FAILED_TO_CONNECT:
		errorMsg = "Failed to connect to the specified server.";
		break;
	case ZT_E_CONNECTION_CLOSED:
		errorMsg = "The connection to the server was unexpectedly closed.";
		break;
	case ZT_E_SEND_FAILED:
		errorMsg = "Failed to send data to the connected server.";
		break;
	case ZT_E_NO_ADAPTER:
		errorMsg = "Failed to query information about the TCP/IP adapter.";
		break;
	case ZT_E_NOT_CONNECTED:
		errorMsg = "The connection is not established.";
		break;
	case ZT_E_WINSOCK_ERROR:
		errorMsg = "A unexpected winsock error occurred.";
		break;
	case ZT_E_RECEIVE_FAILED:
		errorMsg = "Failed to receive data from the connected server.";
		break;
	case ZT_E_WAITMAX_EXCEEDED:
		errorMsg = "The Maximum number of pending operations has been exceeded.";
		break;
	/////////////////////////////////////////////////////////////////////////
	// ZoneTech Authentication Error Codes
	/////////////////////////////////////////////////////////////////////////
	case ZT_E_AUTH_INVALID_STATUS:
		errorMsg = "The ZoneAuth Session status is not in an expected state.";
		break;
	case ZT_E_AUTH_PENDING:
		errorMsg = "The ZoneAuth Session has not yet completed authentication.";
		break;
	case ZT_E_AUTH_TIMEOUT:
		errorMsg = "The ZoneAuth Session was failed because the connection was timed out by the client.";
		break;
	case ZT_E_AUTH_INVALID_PACKET:
		errorMsg = "The ZoneAuth Session received an invalid packet from the server.";
		break;
	case ZT_E_AUTH_USER_CANCEL:
		errorMsg = "The ZoneAuth Session was cancelled by the user.";
		break;
	case ZT_E_AUTH_DENIED:
		errorMsg = "The ZoneAuth Session's credentials failed authentication.";
		break;
	case ZT_E_AUTH_INVALID_TICKET:
		errorMsg = "The ZoneAuth Ticket provided was invalid.";
		break;
	case ZT_E_AUTH_NO_CACHE:
		errorMsg = "There are no cache credentials to provide re-authentication against.";
		break;
	case ZT_E_AUTH_NO_LOGIN_INFO:
		errorMsg = "There is not previously stored login information.";
		break;
	/////////////////////////////////////////////////////////////////////////
	// ZoneTech Match Error Codes
	/////////////////////////////////////////////////////////////////////////
	case ZT_E_MATCH_NO_KEY:
		errorMsg = "The specified key does not exist.";
		break;
	case ZT_E_MATCH_NO_PLAYER:
		errorMsg = "The specified player ID does not exist.";
		break;
	case ZT_E_MATCH_PLAYER_EXISTS:
		errorMsg = "The specified player ID already exists.";
		break;
	case ZT_E_MATCH_RONLY_ATTR:
		errorMsg = "Cannot assign to a read only attribute.";
		break;
	case ZT_E_PROTOCOL_ERROR:
		errorMsg = "An error in the ZoneTech network protocol was encountered.";
		break;
	case ZT_E_BAD_PROTOCOL_VER:
		errorMsg = "Incompatible Protocol version mismatch.";
		break;
	case ZT_E_NO_QUERYSERVER:
		errorMsg = "The ZoneMatch Server for the specified Game GUID is not found.";
		break;
	case ZT_E_ALREADY_RUNNING:
		errorMsg = "Object is already running.";
		break;
	case ZT_E_CLOSED:
		errorMsg = "Object is closed.";
		break;
	case ZT_E_INVALID_COLUMN:
		errorMsg = "Supplied column does not exist.";
		break;
	case ZT_E_DISCONNECTED:
		errorMsg = "Client is disconnected from the server.";
		break;
	case ZT_E_XML_SYNTAX_ERROR:
		errorMsg = "An XML syntax error has be encountered.";
		break;
	case ZT_E_TOO_MANY_ATTRIBUTES:
		errorMsg = "An XML tag has too many attribute fields.";
		break;
	case ZT_E_MISMATCHED_XML_TAGS:
		errorMsg = "The XML Start and End Tags do Not Match.";
		break;
	default:
		errorMsg = "Unknown error code.";
		break;
	}

	return errorMsg;
}

////////////////////////////////////////////////////////////////////////////////////////
//  GetGUNDownloadStats()
//  Purpose:
//       Returns stats on the current server download.  Returns the number of rows
//       in the server query and the current number of rows processed.
//  
//  Parameters:
//       [void] :
//  
//  Returns:  int __stdcall
//  
//  Change Log
//       Author                 MMDDYY  Description
//       --------------------   ------  ------------------------------------------------
//       Michael Moore          092200  Initial Version
//       Michael Moore          101900  Fixed Multithreaded bug
//  
//       
////////////////////////////////////////////////////////////////////////////////////////
void __stdcall GetGUNDownloadStats( int * tableSize, int * progressCount )
{
    if ( g_serverQuery.bActive == true )
    {
	    *tableSize = g_serverQuery.tableSize;
	    *progressCount = g_serverQuery.progressCount;
    }
    else 
    {
        // lock the list for this because callbacks might close the query we are looking at
        Browse::GameList::Lock();

        GUNQuery * pQuery = g_serverQuery.pNext;
        if ( pQuery != NULL )
        {
            // display the status of the secondary query
            *tableSize = pQuery->tableSize;
            *progressCount = pQuery->progressCount;
        }
        else
        {
            // mdm - UI expects to see -1 if the size isn't valid or isn't known. - sam
            *tableSize = -1;
            *progressCount = -1;
        }
        Browse::GameList::Unlock();
    }
}


//========================================================================
//========================================================================
bool __stdcall InitGUNServerBrowser(void)
{
	if (!hasZoneMatch)
    {
		STOP(( "Failed to initialize ZoneMatch.  One or more components may be missing or improperly installed.  Please reinstall the application." ));
    }



	gosASSERT(GUNServerBrowser::m_pGun == NULL);
	GUNServerBrowser::m_pGun = new GUNServerBrowser();

	if (GUNServerBrowser::m_pGun == NULL)
		return NULL;

	GUNServerBrowser::m_pGun->Register();

	return true;
}

//========================================================================
//========================================================================
bool __stdcall GUNServerBrowser::Release(void)
{
	gosASSERT(this == m_pGun);
	Unregister();
	delete this;
	m_pGun = NULL;

	return true;
}



//========================================================================
bool __stdcall GUNServerBrowser::Initialize(void)
{
	m_bManualDisconnect = false;
	gos_ConnectZoneMatch( NULL, 0 );
	Synchronize();
	return true;
}

//========================================================================
//========================================================================
bool __stdcall GUNServerBrowser::Disconnect(void)
{
    CloseGUN();
	return true;
}

//========================================================================
//========================================================================
const char *__stdcall GUNServerBrowser::GetDescription(void)
{
	return "GUN";
}

//========================================================================
//========================================================================
int __stdcall GUNServerBrowser::GetStatus(void)
{
	return true;
}

//========================================================================
//========================================================================
bool __stdcall GUNServerBrowser::Synchronize(void)
{
	//if (Connected && !InNetworkGame)
    if ( IAmTheServer || InNetworkGame )
    {
        return true;
    }
	if ( !m_bManualDisconnect )
	{
	    GUNSynchronize( true );

        // check for a pending update -- fire it off if we're ready
        if ( g_pZMAsync && g_pZMBrowser && (g_UpdatePending || g_RefreshPending) )
        {
            IZoneAsync::STATUS Status;

            g_pZMAsync->GetStatus( &Status );

            if ( IZoneAsync::STATUS_RUNNING == Status )
            {
                if ( g_UpdatePending )
                {
                    StartUpdate();
                }
                else
                {
                    Refresh(NULL); 
                }
            }
        }

        CheckQueries(&g_serverQuery);
        CheckQueries(&g_playerQuery);
	}
	return true;
}

////////////////////////////////////////////////////////////////////////////////////////
//  GUNServerBrowser::CheckQueries()
//  Purpose:
//       Check all active queries for timeouts or errors
//  
//  Parameters:
//       [pQuery] - head of the query list to check
//  
//  Returns:  none
//  
//  Change Log
//       Author                 MMDDYY  Description
//       --------------------   ------  ------------------------------------------------
//       Michael Moore          100100  Initial Version
//  
//       
////////////////////////////////////////////////////////////////////////////////////////
void GUNServerBrowser::CheckQueries( GUNQuery * pQuery )
{
    while ( pQuery )
    {
        if ( pQuery->bActive == true )
        {
            if ( gos_GetHiResTime() - pQuery->lastUpdateTime > QUERY_TIMEOUT )
            {
                // timeout the query unless its the main query -- in which case we let it go forever if it wants
                // because user can cancel manually
                if ( pQuery != &g_serverQuery && pQuery != &g_playerQuery)
                {
                    GUNCloseQuery(pQuery, true);
                    return; // cuz we can't handle removal from list... this is called every frame so we'll catch multiples on the next go around.
                }
            }
        }
        pQuery = pQuery->pNext;
    }
}

//========================================================================
//========================================================================
BOOL PASCAL GUNServerBrowser::EnumSessionsCallback( LPCDPSESSIONDESC2 lpSessionDesc, LPDWORD lpdwTimeOut, DWORD dwFlags, void* lpContext )
{
	return FALSE;
}

//========================================================================
// PrepareJoinGame
//
// This is called just before open is called on a game. In here, we must
// set the DirectPlay object to be the correct protocol and to correctly
// enumerate the game on the LAN
//
//========================================================================
bool __stdcall GUNServerBrowser::PrepareJoinGame(const char * szGameName, void **ppDPlay)
{
    EnterCriticalSection( &EnumCriticalSection );

	IDirectPlay4A *				lpDP = NULL;
	IDirectPlayLobby3 *			lpDPL = NULL;
	HRESULT						hr;

	//
	// get the IP address of the game in question
	//
	const char *szIPAddress;
	const char *szGameIdentifier;
    szGameIdentifier = GetGameList()->GetIDFromField( GAMELIST_KEY_SERVER_NAME, szGameName );
	szIPAddress = GetGameList()->GetDataField(szGameIdentifier, GAMELIST_KEY_SERVER_ADDRESS);
	const char *szPort = GetGameList()->GetDataField(szGameIdentifier, GAMELIST_KEY_SERVER_PORT);
	int port;
	if (szPort)
		port = atoi(szPort);
	else
		port = 0;

    // GUN must do special translation because zero is not allowed
    if ( port == 1 )
    {
        port = 0;
    }


	//
	// create the DirectPlay object
	//
	if (FAILED(hr = CoCreateInstance( CLSID_DirectPlay, NULL, CLSCTX_ALL, IID_IDirectPlay4A, (VOID**)&lpDP)))
		goto CLEANUP;

	//
	// release the original GameOS DirectPlay object
	//
	if (*(IDirectPlay4A **)ppDPlay)
	{
		(*(IDirectPlay4A **)ppDPlay)->Release();
		*(IDirectPlay4A **)ppDPlay = NULL;
	}
	
	//
	// copy the DirectPlay object back to the GameOS DirectPlay object
	//
	*(IDirectPlay4A **)ppDPlay = lpDP;

	//
	// create a lobby object
	//

	//
	// create a DirectPlayLobby object for creation of an address
	//
	if (FAILED(hr = CoCreateInstance( CLSID_DirectPlayLobby, NULL, CLSCTX_ALL, IID_IDirectPlayLobby3, (VOID**)&lpDPL)))
		goto CLEANUP;

	//
	// create the address needed
	//
	DPCOMPOUNDADDRESSELEMENT comAdd[3];
	DWORD Size;

	void* p_address;

	comAdd[0].guidDataType	= DPAID_ServiceProvider;
	comAdd[0].dwDataSize	= sizeof(GUID);
	comAdd[0].lpData		= (void*)&DPSPGUID_TCPIP;
	comAdd[1].guidDataType	= DPAID_INet;								// Create a compund address
	comAdd[1].dwDataSize	= strlen(szIPAddress) + 1;
	comAdd[1].lpData		= (void *)szIPAddress;
	comAdd[2].guidDataType	= DPAID_INetPort;
	comAdd[2].dwDataSize	= sizeof(port);
	comAdd[2].lpData		= (void *)&port;

	int num_elements;
	if (port)
		num_elements = 3;
	else
		num_elements = 2;


	//
	// create the address
	//
	lpDPL->CreateCompoundAddress(comAdd, num_elements, NULL, &Size );
	p_address = (void*)gos_Malloc(Size);
	if (FAILED(hr = lpDPL->CreateCompoundAddress(comAdd, num_elements, p_address, &Size )))
		goto CLEANUP;

	//
	// initialize the connection
	//
	if (FAILED(hr = lpDP->InitializeConnection(p_address, 0)))
		goto CLEANUP;

	//
	// enumerate
	//
	if (true)
	{
		DPSESSIONDESC2	dpsd;
		memset( &dpsd, 0, sizeof(dpsd) );

		dpsd.dwSize		= sizeof(dpsd);
		memcpy( &dpsd.guidApplication, &Environment.NetworkGUID, sizeof(GUID) );
		DWORD dwFlags = DPENUMSESSIONS_RETURNSTATUS | DPENUMSESSIONS_ALL | DPENUMSESSIONS_PASSWORDREQUIRED;

		if (FAILED(hr = lpDP->EnumSessions(&dpsd, 0, EnumSessionsCallback, 0, dwFlags )))
			goto CLEANUP;
	}

    LeaveCriticalSection( &EnumCriticalSection );

    return true;

CLEANUP:
	if (lpDPL)
	{
		lpDPL->Release();
		lpDPL=NULL;
	}
    LeaveCriticalSection( &EnumCriticalSection );
    return false;

}

//========================================================================
//========================================================================
bool __stdcall GUNServerBrowser::PrepareRefresh(void)
{
    return true;
}


////////////////////////////////////////////////////////////////////////////////////////
//  GUNServerBrowser::Refresh()
//  Purpose:
//       Refresh the information on the indicated game.  If gameID is NULL, then all 
//       unfiltered games will be refreshed (this is how refresh(NULL) differs from 
//       Update - games that are filtered out aren't included)
//  
//  Parameters:
//       [*gameID] :
//  
//  Returns:  bool __stdcall
//  
//  Change Log
//       Author                 MMDDYY  Description
//       --------------------   ------  ------------------------------------------------
//       Michael Moore          093000  Initial Version
//  
//       
////////////////////////////////////////////////////////////////////////////////////////
bool __stdcall GUNServerBrowser::Refresh(char *gameID)
{
    Browse::GameList * gameList = ServerBrowser::GetGameList();

    if ( gameList == NULL )
    {
        return false;
    }


    // if we're not yet running, set up a pending refresh
	if ( g_pZMAsync == NULL )
	{
		goto RESTART;
	}

	IZoneAsync::STATUS Status;
	g_pZMAsync->GetStatus( &Status );

    // if async interface is operating, it is safe to create a new query
    if( Status != IZoneAsync::STATUS_RUNNING )
    {
        // now, if GUN is down we just want to try and reconnect
        if( Status == IZoneAsync::STATUS_FAILED || Status == IZoneAsync::STATUS_CANCELLED ||
            Status == IZoneAsync::STATUS_COMPLETED || Status == IZoneAsync::STATUS_STOPPING ||
            Status == IZoneAsync::STATUS_CREATED )
        {
            // we only get here if the ZoneMatch connection is down, so we want
            // to call disconnect and try again.
            // restart everything 
RESTART:
            // we're trying to download, but special steps are required... mark us as busy
            Disconnect();
        }
        g_RefreshPending = true;
        return true;
    }

    /////////////////////////////////////////////////////////
    // If we get here, we are ready to fire off a refresh ///
    /////////////////////////////////////////////////////////

    g_RefreshPending = false;

    // fire off a status query for the server
	GUNQuery * pQuery = new GUNQuery;

	// add query to the front of the server list of dynamic queries
	pQuery->pNext = g_serverQuery.pNext;
	g_serverQuery.pNext = pQuery;

	pQuery->queryStyle = GQS_ONESHOT;
	pQuery->queryType = GQT_SERVER;
    pQuery->bIsFullUpdate = false;

    // form rowstring - first question: is this a single game or all unfiltered games?
    if ( gameID != NULL )
    {
        pQuery->pFilter = (char *) malloc( strlen(GUN_ROWID_STR) + strlen(gameID) + 2 );
        sprintf(pQuery->pFilter, "%s=%s", GUN_ROWID_STR, gameID );
        // mark rows in gamelist as expecting an update
        QueryMarkRefresh( pQuery, gameID);
    }
    else
    {
        // figure out if we want to do a full update or a rowid-filtered refresh
	    Browse::IndexedTableView * indexView = gameList->GetIndexedView();
	    gosASSERT(indexView);
	    int numGames = indexView->GetCount();
        // base the decision on whether to request a full update on the percentage of the
        // list that is visible vs. the total size of the list
        if ( g_ServerListSize <= MIN_SERVER_LIST_SIZE_FOR_REFRESH || 
             ( (float)( (float) numGames / g_ServerListSize) > MAX_REFRESH_LIST_PERCENTAGE ) )
        {
            pQuery->bIsFullUpdate = true;
            pQuery->pFilter = NULL;
        }
        else
        {
            // compute a rowfilter string
            pQuery->pFilter = NULL;
            int size = 0;
            for ( int allocate = 0; allocate < 2; allocate++ )
            {
                for ( int i = 0; i < numGames; i++ )
                {
                    // get the gameID
                    char * pGameID;

                    if ( indexView->GetIndex( i, &pGameID) == false )
                    {
                        PAUSE(( "GetIndex() failed for gamelist index %d.\n", i ));
                        continue;
                    }

                    // find out the protocol type for the game
                    const char * pProtocol = gameList->GetDataField( pGameID, GAMELIST_KEY_PROTOCOL );

                    if ( pProtocol == NULL )
                    {
                        // no such game or no protocol identified for it.
                        continue;
                    }

                    if ( strcmp(pProtocol, GAMELIST_VALUE_PROTOCOL_GUN) )
                    {
                        // not a gun game
                        continue;
                    }

                    if ( pQuery->pFilter )
                    {
                        strcat(pQuery->pFilter, pGameID); // gameIDs are rowids for GUN games
                        strcat(pQuery->pFilter, ",");
                        // mark rows in gamelist as expecting an update
                        QueryMarkRefresh( pQuery, pGameID);
                    }
                    else
                    {
                        size += strlen(pGameID) + 1;
                    }
                }

                // allocate or tidy up the pFilter
                if ( pQuery->pFilter )
                {
                    // remove ending comma if there was one
                    if ( size )
                    {
                        pQuery->pFilter[strlen(pQuery->pFilter)-1] = 0;
                    }
                    else
                    {
                        // else there were no games matching, so there should be no query.
                        GUNCloseQuery(pQuery);
                        return true;
                    }
                }
                else
                {
                    // allocate pQuery->pFilter
                    pQuery->pFilter = (char *) malloc( size + strlen(GUN_ROWID_STR) + 16); // 16 just because I am lazy

                    // kick off the string in prep for next iteration
                    sprintf(pQuery->pFilter, "%s=", GUN_ROWID_STR);
                }
            }
        }

        // note we also may go with a full update if the length of the refresh row filter is too large.
        if ( pQuery->bIsFullUpdate || !pQuery->pFilter || 
            (strlen(pQuery->pFilter) > MAX_REFRESH_FILTER_LENGTH ) )
        {
            // perform a full update
            if ( pQuery->pFilter )
            {
                free(pQuery->pFilter);
            }
	        pQuery->pFilter = NULL;
            pQuery->bIsFullUpdate = true;
            // mark all gun games as being refreshed
            QueryMarkAllRefresh( pQuery );
        }

    }

	return GUNStartQuery( pQuery );
}

//========================================================================
//========================================================================
bool __stdcall GUNServerBrowser::RefreshServerInfo(char *gameID)
{
    Browse::GameList * gameList = ServerBrowser::GetGameList();

    if ( gameList == NULL )
    {
        return false;
    }

    // find out the protocol type for the game
    const char * pProtocol = gameList->GetDataField( gameID, GAMELIST_KEY_PROTOCOL );

    if ( pProtocol == NULL )
    {
        // no such game or no protocol identified for it.
        return false;
    }

    // see if the protocl matches our type
    if ( strcmp( pProtocol, GAMELIST_VALUE_PROTOCOL_GUN ) )
    {
        // this isn't us
        return false;
    }

    // mdm - change this if we want to spawn a player query for the specific server.
    //       right now all player data is downloaded with an update so we don't have any
    //       work to do.
    // assume we already have the player data, so set success right away.

	// remove players from the game if they exist
	Browse::PlayerList * playerList = gameList->GetPlayerList(gameID);
	if ( playerList )
	{
		playerList->RemoveAll();
	}


    // fire off a player query for the indicated game
    GUNQuery * pQuery = new GUNQuery;

    // add query to the front of the player list of dynamic queries
    pQuery->pNext = g_playerQuery.pNext;
    g_playerQuery.pNext = pQuery;

    pQuery->queryStyle = GQS_ONESHOT;
    pQuery->queryType = GQT_PLAYER;
    pQuery->bIsServerInfo = true;


	pQuery->pFilter = (char *) malloc( strlen(PLAYERLIST_KEY_SERVER_ROWID) + strlen(gameID) + 2 );
	sprintf(pQuery->pFilter, "%s=%s", PLAYERLIST_KEY_SERVER_ROWID, gameID);

	pQuery->pGameID = (char *) malloc( strlen( gameID ) + 1 );
	strcpy(pQuery->pGameID, gameID);

	if ( GUNStartQuery( pQuery ) == false )
	{
		return false;
	}

	// fire off a status query for the server too
	pQuery = new GUNQuery;

	// add query to the front of the server list of dynamic queries
	pQuery->pNext = g_serverQuery.pNext;
	g_serverQuery.pNext = pQuery;

	pQuery->queryStyle = GQS_ONESHOT;
	pQuery->queryType = GQT_SERVER;

	pQuery->pFilter = (char *) malloc( strlen(GUN_ROWID_STR) + strlen(gameID) + 2 );
	sprintf(pQuery->pFilter, "%s=%s", GUN_ROWID_STR, gameID);

    QueryMarkRefresh( pQuery, gameID );

	pQuery->pGameID = (char *) malloc( strlen( gameID ) + 1 );
	strcpy(pQuery->pGameID, gameID);

	return GUNStartQuery( pQuery );
}

int  GUNSynchronize( bool bStartQueries )
{
	//
	// Trigger ZoneTech enum
	//
    if (!g_pZMBrowser)
    {
        GUNStatus = GS_INIT_BROWSER;

    //
    // Create browser object and initiate connect to server
    //
	    HRESULT hr=E_FAIL;
	
	    IUnknown* punk=NULL;

        wCoCreateInstance(
				    CLSID_ZoneMatchBrowser,
				    NULL,       // punkOuter
				    CLSCTX_INPROC,
				    //IID_IZoneMasterBrowser,
				    IID_IUnknown,
				    (PVOID*) &punk);

	    wQueryInterface(punk, IID_IZoneMatchBrowser, (PVOID*)&g_pZMBrowser );
	    wQueryInterface(punk, IID_IZoneAsync,         (PVOID*)&g_pZMAsync );

	    // Don't need this anymore.
	    wRelease(punk);

	    // Load the connection parameters.
	    //
	    gosASSERT(Environment.ZoneMatchServerIP);
	    hr = g_pZMBrowser->InitZMBrowser( Environment.ZoneMatchServerIP,
									    MASTER_SERVER_PORT,
									    Environment.GameGUID,
                                        NULL,
									    NULL);
	    if( FAILED( hr ) )
	    {
            GUNStatus = -GS_INIT_BROWSER;
			GUNLastError = hr;
            SPEW(( GUN_SPEW_ID, "No query server available to obtain server list from.\n") );
            if ( g_pZMAsync )
            {
                wRelease(g_pZMAsync);
                g_pZMAsync = NULL;
            }

            if ( g_pZMBrowser )
            {
                wRelease(g_pZMBrowser);
                g_pZMBrowser = NULL;
            }

        	GUNNetStatus &= ~ServerBrowser::GNS_DOWNLOADING_SERVERS;
            GUNNetStatus &= ~ServerBrowser::GNS_DOWNLOADING_PLAYERS;
            g_UpdatePending = false;
            g_RefreshPending = false;
//		    PAUSE(( "FAILED (0x%x - %s) - IZoneMaterBrowser::InitZMBrowser\n ",hr,ErrorNumberToMessage(hr)));
            return 0;
	    }

        GUNStatus = GS_FIND_QSERVER;
	    hr = g_pZMAsync->Start( NULL );
	    if( S_OK != hr )
	    {
		    if( hr != ZT_E_PENDING )
		    {
        		GUNNetStatus &= ~ServerBrowser::GNS_DOWNLOADING_SERVERS;
                GUNNetStatus &= ~ServerBrowser::GNS_DOWNLOADING_PLAYERS;
                g_UpdatePending = false;
                g_RefreshPending = false;
                GUNStatus = -GS_FIND_QSERVER;
				GUNLastError = g_pZMAsync->GetLastError();
		    }
	    }

    }
    else
    {

    //
    // Initiate getting data from ZoneTech master server
    //
	    if (g_pZMAsync && g_pZMBrowser)
	    {
		    IZoneAsync::STATUS Status;

		    g_pZMAsync->GetStatus( &Status );

		    // If it hasn't started up yet, then return and come back on
		    // the next timer tick.
		    //
		    if( IZoneAsync::STATUS_STARTING != Status )
		    {
			    if( IZoneAsync::STATUS_RUNNING == Status )
                {
    			    ZoneMatchConnected=TRUE;
                    GUNStatus = GS_CONNECTED;
 			    }
			    else
			    {
        			GUNNetStatus &= ~ServerBrowser::GNS_DOWNLOADING_SERVERS;
                    GUNNetStatus &= ~ServerBrowser::GNS_DOWNLOADING_PLAYERS;
                    g_UpdatePending = false;
                    g_RefreshPending = false;
                    ZoneMatchConnected = FALSE;
                    GUNStatus = -GS_FIND_QSERVER;
					GUNLastError = g_pZMAsync->GetLastError();
			    }
            }
	    }
    }
    return 0;
}

bool _stdcall GUNPrepareDPlay( const char * GameName )
{
    const char * serverID;
    const char * pServer = 0;
    char pServerAddr[512];
    const char * pPort = 0;
    int serverPort = -1;

    EnterCriticalSection( &EnumCriticalSection );

    // figure out the server IP and port we want
    // to connect to.
    serverID = ServerBrowser::GetGameList()->GetIDFromField( GAMELIST_KEY_SERVER_NAME,
        GameName );
    if ( serverID == NULL )
    {
        // could not find the game they indicated
        PAUSE(( "Unable to find game %s on the network.", GameName ));
        goto CLEANUP;
    }

    // get the server IP address
    pServer = ServerBrowser::GetGameList()->GetDataField(serverID, GAMELIST_KEY_SERVER_ADDRESS );
    if ( pServer == NULL )
    {
        // couldn't find the server ip address!
        PAUSE(( "Unable to find server ip address for serverID %d, gamename %s.", serverID, GameName ));
        goto CLEANUP;
    }

    // make local copy so that we don't
    strcpy(pServerAddr, pServer);

    // get the server port
    pPort = ServerBrowser::GetGameList()->GetDataField(serverID, GAMELIST_KEY_SERVER_PORT );
    serverPort = atoi(pPort);
    //if ( pPort == NULL || serverPort <= 0 )
    //{
    //    // either there was no port information found
    //    // or they specified and invalid port
    //    PAUSE(( "Unable to find port number for serverID %d, gamename %s.", serverID, GameName ));
    //    goto CLEANUP;
    //}

    LeaveCriticalSection( &EnumCriticalSection );

    // if we made it this far, we're ready to reconnect
    // using TCP with a specific server IP address
    // and port.
    gos_ShutdownNetwork();

    gos_ConnectTCP( pServerAddr, 0 );//(unsigned short) serverPort );
    Connected = NP_ZONEMATCH;
    return true;

CLEANUP:
    LeaveCriticalSection( &EnumCriticalSection );
    return false;
}

////////////////////////////////////////////////////////////////////////////////////////
//  PushGameList()
//  Purpose:
//       Pushes the game list maintained by GUNGameList to the internal game list 
//       GameOS expects.
//  
//  Parameters:
//       [void] :
//  
//  Returns:  none
//  
//  Change Log
//       Author                 MMDDYY  Description
//       --------------------   ------  ------------------------------------------------
//       Michael Moore          083100  Initial Version
//       Michael Moore          100300  Push entire gamelist if there is no selection 
//                                       (for backward compatibility)
//  
//       
////////////////////////////////////////////////////////////////////////////////////////
void _stdcall PushGameList( void )
{
    EnterCriticalSection( &EnumCriticalSection );


    GUNFreeGOSListOfGames(GameNames);
    GameNames = 0;

	if (ServerBrowser::GetGameList() == NULL)
    {
        LeaveCriticalSection( &EnumCriticalSection );
		return;
    }

    Browse::IndexedTableView * indexView = ServerBrowser::GetGameList()->GetIndexedView();
    gosASSERT(indexView);


    // mdm - we know that the index that is currently selected in the indexed view is  the
    // same as the game they want to join.  So, to avoid problems where more than one game
    // has the same name in the list, we will only push the game that is at the index
    // that is currently selected.  That way, GameOS InternalJoinGame() can't go wrong.
    // If there is no selection, then we will push all games just to be backward compatible.

    char * gameID = (char *) indexView->GetSelection();

    if ( gameID == NULL )
    {
        // enumerate entire list to GOS
        for ( int i = 0; i < indexView->GetCount(); i++)
        {
            if ( indexView->GetIndex( i, &gameID ) == false )
            {
                PAUSE(( "Failed to retreive gameID for game at index %d.\n", i ));
                continue; // skip it
            }
            PushGameToGOS( gameID );
        }
    }
    else
    {
        PushGameToGOS( gameID );
    }


    LeaveCriticalSection( &EnumCriticalSection );
}

void PushGameToGOS( const char * gameID )
{
	if (ServerBrowser::GetGameList() == NULL)
		return;

    // mdm - First determine the name of the game server
    const char * serverName = ServerBrowser::GetGameList()->GetDataField( gameID, GAMELIST_KEY_SERVER_NAME);

    if ( serverName == NULL )
    {
        PAUSE(( "Servername in PushGameToGOS was NULL.\n" ));
        return; // invalid
    }

    ListOfGames* pThis=(ListOfGames*)malloc( sizeof(ListOfGames) + strlen(serverName) );

	memset( &pThis->SessionDescriptor, 0, sizeof(pThis->SessionDescriptor) );
    strcpy( pThis->Name, serverName );


	GUID	guidInstance;
    if ( ServerBrowser::GetGameList()->GetGUID( gameID, guidInstance ) == false )
    {
        // no such GUID -- shouldn't happen
        STOP(( "PushGameList unable to get GUID for gameID %d.", gameID ));
        // handle error better here
    }

    // find the column index for the password header,
    // which tells us if the game is password-protected
    const char * password = ServerBrowser::GetGameList()->GetDataField( gameID,
        GAMELIST_KEY_PASSWORD_PROTECTED );

    if ( password )
    {
        if (!stricmp(password, "yes"))
        {
            pThis->SessionDescriptor.dwFlags |= DPSESSION_PASSWORDREQUIRED;
        }
    }

	memcpy(&pThis->SessionDescriptor.guidInstance,&guidInstance,sizeof(GUID));


//
// Initialize rest of structure
//
	pThis->pPlayers=0;
	pThis->pNext=0;
//
// Add it to the end of the current list
//
    int indexCount = 0;

	ListOfGames* pCurrent=GameNames;
	if( pCurrent )
	{
		while( pCurrent->pNext )
		{
			pCurrent=pCurrent->pNext;
            indexCount++;
		}

		pCurrent->pNext=pThis;
        indexCount++;
	}
	else
	{
		GameNames = pThis;
	}

}


////////////////////////////////////////////////////////////////////////////////////////
//  FieldExcluded()
//  Purpose:
//       Returns true if the indicated string matches a field header that is excluded 
//       from query headers
//  
//  Parameters:
//       [pStr] :
//  
//  Returns:  bool
//  
//  Change Log
//       Author                 MMDDYY  Description
//       --------------------   ------  ------------------------------------------------
//       Michael Moore          091300  Initial Version
//  
//       
////////////////////////////////////////////////////////////////////////////////////////
bool IsGUNFieldExcluded( LPCSTR pStr )
{
	if ( !strcmp(pStr, GAMELIST_KEY_PRODUCT_NAME) )
	{
		return true;
	}

	if ( !strcmp(pStr, GAMELIST_KEY_MOD_NAME) )
	{
		return true;
	}

	return false;
}



void GUNFreeHeaders( GUNQuery * query )
{
    gosASSERT(query);

    if ( query->pHeaders )
    {
        query->pRowMgr->FreeRow( query->pHeaders );
        query->pHeaders = NULL;
    }
}


void GUNStoreHeaders( GUNQuery * query,
                      LPZONEMATCHROW pRow )
{
    gosASSERT(query);

    // ensure any previous headers are freed.
    GUNFreeHeaders( query );

	LPCSTR * trimmedValues = (LPCSTR *) malloc( 
		pRow->Count() * sizeof( char * ) );

	int count = 0;
	for ( int i = 0; i < pRow->Count(); i++)
	{
		LPCSTR pStr = pRow->Strings(i);

		if ( !pStr )
		{
			continue;
		}

		if ( IsGUNFieldExcluded( pStr ) )
		{
			continue;
		}

		// if we make it this far then the header
		// string is valid and should be added to our
		// list.
		trimmedValues[count] = pStr;
		count++;
	}

	// allocate a new row that has any unwanted headers removed
	HRESULT hr = query->pRowMgr->AllocateRow( count, trimmedValues, &(query->pHeaders) );

	free(trimmedValues);

    if ( FAILED(hr) )
    {
        query->pHeaders = NULL;
        return; 
    }
}

//
// used to set propertys to advertise game
// player = 0 means server property
// strings passed through to server via ZoneTech so integers not interpreted
//

bool __stdcall gos_NetSetAdvertItem( DWORD player, const char * Name , const char * Value);


void GUNFreeGOSListOfGames( ListOfGames * gameList )
{
    //
    // Clear any games/players used last frame
    //
	while( gameList )
	{
		ListOfGames* pThis=gameList;
		gameList=gameList->pNext;
		if( pThis->pPlayers )
		{
			ListOfNames* pTemp=pThis->pPlayers;
			while( pTemp )
			{
				ListOfNames* pThis=pTemp;			// Remove list of player names
				pTemp=pTemp->pNext;
				free( pThis );
			}
		}
		free( pThis );								// Remove gane name and information
	}
}


////////////////////////////////////////////////////////////////////////////////////////
//  GUNBrowser::CancelActivity
//  Purpose:
//       Cancel the current GUN gamelist query, if active.
//  
//  Parameters:
//       [void] :
//  
//  Returns:  none
//  
//  Change Log
//       Author                 MMDDYY  Description
//       --------------------   ------  ------------------------------------------------
//       Michael Moore          082500  Initial Version
//  
//       
////////////////////////////////////////////////////////////////////////////////////////
bool __stdcall GUNServerBrowser::CancelActivity(void)
{
    // cancel the update of the game list.  this amounts to calling
    // stopdata on the current view and then closing it.
    EnterCriticalSection( &EnumCriticalSection );
    GUNCloseAllQueries();
    LeaveCriticalSection( &EnumCriticalSection );

	if ( GUNStatus >= 0 && 
		 GUNStatus != GS_CONNECTED && 
		 !m_bManualDisconnect)
	{
        Disconnect();
		m_bManualDisconnect = true;
	}

    // make sure ping server is up and running.
    if ( ServerBrowser::GetGameList() )
    {
        ServerBrowser::GetGameList()->StartPingServer();
        ServerBrowser::GetGameList()->StartPingClient( ZONE_PING_INTERVAL );
    }

    g_UpdatePending = false;
    g_RefreshPending = false;

	return true;
}




////////////////////////////////////////////////////////////////////////////////////////
//  GUNServerBrowser::StartUpdate()
//  Purpose:
//       Start a new game list update.
//  
//  Parameters:
//       [void] :
//  
//  Returns:  none
//  
//  Change Log
//       Author                 MMDDYY  Description
//       --------------------   ------  ------------------------------------------------
//       Michael Moore          082500  Initial Version
//  
//       
////////////////////////////////////////////////////////////////////////////////////////
bool __stdcall GUNServerBrowser::StartUpdate(void)
{
	m_bManualDisconnect = false;
	if ( g_pZMAsync == NULL )
	{
		goto RESTART;
	}

	IZoneAsync::STATUS Status;
	g_pZMAsync->GetStatus( &Status );

    // if async interface is operating, it is safe to create a new query
    if( Status == IZoneAsync::STATUS_RUNNING &&
        g_serverQuery.bActive == false )
    {
        EnterCriticalSection( &EnumCriticalSection );

        // start server query
        g_serverQuery.queryType = GQT_SERVER;
        g_serverQuery.queryStyle = GQS_ONESHOT;
        g_serverQuery.bIsFullUpdate = true;
        if ( GUNStartQuery( &g_serverQuery ) == false )
        {
            GUNStatus = -GS_QUERY_SERVERS;
			GUNLastError = E_FAIL;
            PAUSE(( "Failed to initiate server query" ));
        }
        else
        {
            if ( g_bDownloadPlayers )
            {
                g_playerQuery.queryType = GQT_PLAYER;
                g_playerQuery.queryStyle = GQS_ONESHOT;
                if ( GUNStartQuery( &g_playerQuery ) == false ) 
                {
                    GUNStatus = -GS_QUERY_PLAYERS;
					GUNLastError = E_FAIL;
                    PAUSE(( "Failed to initiate server query" ));
                }
            }
        }
        g_UpdatePending = false; // we got the update to fire.
        LeaveCriticalSection( &EnumCriticalSection );
    }
    else
    {
        // now, if GUN is down we just want to try and reconnect
        if( Status == IZoneAsync::STATUS_FAILED || Status == IZoneAsync::STATUS_CANCELLED ||
            Status == IZoneAsync::STATUS_COMPLETED || Status == IZoneAsync::STATUS_STOPPING ||
            Status == IZoneAsync::STATUS_CREATED )
        {
            // we only get here if the ZoneMatch connection is down, so we want
            // to call disconnect and try again.
            // restart everything 
RESTART:
            // we're trying to download, but special steps are required... mark us as busy
            Disconnect();
        }
        g_UpdatePending = true;
    }

	return true;
}

int __cdecl compare( const void * elem1, const void * elem2 )
{
    int l,r;
    l = (*(int *)elem1);
    r = (*(int *)elem2);
    return l - r;
}

////////////////////////////////////////////////////////////////////////////////////////
//  GUNServerBrowser::CompressRowFilter()
//  Purpose:
//       This is used to reduce rowfilters that are based on rowids so that they use 
//       X-Y instead of each individual number in the rowfilter string
//  
//  Parameters:
//       [rowFilter] :
//  
//  Returns:  none
//  
//  Change Log
//       Author                 MMDDYY  Description
//       --------------------   ------  ------------------------------------------------
//       Michael Moore          093000  Initial Version
//  
//       
////////////////////////////////////////////////////////////////////////////////////////
void GUNServerBrowser::CompressRowFilter( char * rowFilter )
{
    // find out how many commas are in rowFilter
    int numElements = 1;
    char * pChar = rowFilter;

    if ( rowFilter == NULL )
    {
        return;
    }

    while ( *pChar != NULL )
    {
        if ( *pChar == ',') numElements++;
        pChar++;
    }

    if ( numElements == 1 )
    {
        return;
    }

    while ( *rowFilter != NULL && *rowFilter != '=')
    {
        rowFilter++;
    }

    if ( rowFilter == NULL ) return; // bad rowid filter string

    rowFilter++; // skip  '='

    int * values = (int *) malloc( sizeof(int) *(numElements) );

    int * value = values;

    pChar = strtok(rowFilter, ",");
    while ( pChar != NULL )
    {
        *value = atoi(pChar);
        value++;
        pChar = strtok(NULL, ",");
    }

    qsort( values, numElements, sizeof(int), compare);

    int i = 0;
    sprintf(rowFilter, "%s=", GUN_ROWID_STR);
    while (i < numElements)
    {
        char range[128];

        int startRange = values[i];
        int endRange = startRange;

        while ( i < numElements && (values[++i] == endRange+1) )
        {
            endRange++;
        }

        if ( endRange == startRange )
        {
            // no range at all
            sprintf(range, "%d", startRange);
        }
        else if ( endRange == startRange + 1)
        {
            // not worth using range syntax when only two in range
            sprintf(range, "%d,%d", startRange, endRange);
        }
        else
        {
            // a real range-- wow, compression at work
            sprintf(range, "%d-%d", startRange, endRange);
        }

        strcat(rowFilter, range);
        if (i != numElements)
        {
            strcat(rowFilter, ",");
        }
    }

}

//========================================================================
//========================================================================
bool __stdcall GUNServerBrowser::GetNetStatus(void)
{
	if ( g_UpdatePending || g_RefreshPending ||
         (GUNNetStatus & GNS_DOWNLOADING_SERVERS) ||
		 (GUNNetStatus & GNS_DOWNLOADING_PLAYERS) )
		return true;

	// say we aren't done if we're not yet connected
	if ( GUNStatus != GS_CONNECTED && GUNStatus > 0)
	{
		if ( GUNStatus != GS_CLOSED )
		{
			return true;
		}
	}

	return false;
}

////////////////////////////////////////////////////////////////////////////////////////
//  GUNStartQuery()
//  Purpose:
//       Start a Query with the ZoneMatch server.  Right now this is either a server 
//       query or a player query.
//  
//  Parameters:
//       [pQuery]  :  pointer to GUNQuery struct describing type of query.   This
//                    structure will be filled in by this function.
// 
// 
//  
//  Returns:  bool
//  
//  Change Log
//       Author                 MMDDYY  Description
//       --------------------   ------  ------------------------------------------------
//       Michael Moore          082400  Initial Version
//       Michael Moore          082800  Now takes GUNQuery pointer as argument
//  
//       
////////////////////////////////////////////////////////////////////////////////////////
bool GUNStartQuery( GUNQuery * pQuery )
{
	HRESULT hr=S_OK;
	LPZONEMATCHROW pRow=NULL;
	IZoneMatchTable *pTable=NULL;
    IZoneAsync::STATUS Status;
    IZoneMatchTableView::LPFN_ZMTABLE_DATA pfnUpdate = NULL;

    if ( g_pZMBrowser == NULL || g_pZMAsync == NULL )
    {
		GUNLastError = E_FAIL;
        return false;
    }

    g_pZMAsync->GetStatus( &Status );
    if( Status != IZoneAsync::STATUS_RUNNING )
    {
		GUNLastError = E_FAIL;
        return false; // browser not ready.
    }

    //
	// Get table object from browser
	//
    switch (pQuery->queryType)
    {
    case GQT_PLAYER:
        hr = g_pZMBrowser->CreateTable( NULL,
							        IID_IZoneMatchTable,
							        IZoneMatchBrowser::TABLE_PLAYER,
							        (PVOID*)&pTable );
        pfnUpdate = GUNUpdatePlayerCB;
        GUNNetStatus |= ServerBrowser::GNS_DOWNLOADING_PLAYERS;
        break;

    case GQT_SERVER:
        hr = g_pZMBrowser->CreateTable( NULL,
							        IID_IZoneMatchTable,
							        IZoneMatchBrowser::TABLE_SERVER,
							        (PVOID*)&pTable );
        pfnUpdate = GUNUpdateServerCB;
        GUNNetStatus |= ServerBrowser::GNS_DOWNLOADING_SERVERS;
        break;

    default:
        PAUSE(( "Invalid Query Type %d requested in GUNStartQuery().", pQuery->queryType ));
        return false;
        break;
    };


    if( FAILED( hr ) )
    {
		GUNLastError = hr;
	    PAUSE(( "FAILED (0x%x - %s) - IZoneMatchBrowser::CreateTable\n ",hr,ErrorNumberToMessage(hr)));
        return false;
    }

    if (pTable)
    {
	    //
	    // Need row allocator to help manage returned results
	    //

	    wQueryInterface(pTable, IID_IZoneRowManager,
                        (PVOID*)&(pQuery->pRowMgr) );

	    if (pQuery->pRowMgr)
        {
		    //
		    // Get column definition for game
		    //
		    hr = pTable->GetAllHeaders( &pRow );

		    if( SUCCEEDED( hr ) )
		    {
                GUNStoreHeaders( pQuery, pRow );
				
                //
			    // Create view using all headers
			    //
			    hr = pTable->CreateView( NULL, IID_IZoneMatchTableView,
								      pQuery->pHeaders, pQuery->pFilter, NULL, true,
								      (PVOID*) &(pQuery->pZMView) );

			    pQuery->pRowMgr->FreeRow(pRow);

			    if (SUCCEEDED(hr))
			    {
				    //
				    // Initiate getting data
				                                  
				    hr = pQuery->pZMView->StartData(               
                        (pQuery->queryStyle == GQS_UPDATING), // true -> continuous updates
                        pfnUpdate,                            // Callback function invoked when updates occur
                        pQuery);                              // context data for use by us.

				    if( FAILED( hr ) )
				    {
						GUNLastError = hr;
					    PAUSE(( "FAILED (0x%x - %s) - IZoneMasterTableView::StartData\n ",hr,ErrorNumberToMessage(hr)));
                        goto CLEANUP;
				    }
			    }
			    else
			    {
					GUNLastError = hr;
				    PAUSE(( "FAILED (0x%x - %s) - IZoneMasterTable::CreateView\n ",hr,ErrorNumberToMessage(hr)));
                    goto CLEANUP;
			    }

		    }
		    else
		    {
				GUNLastError = hr;
			    PAUSE(( "FAILED (0x%x - %s) - IZoneMasterBrowser::GetAllHeaders\n ",hr,ErrorNumberToMessage(hr)));
		    }
	    }
							
	    wRelease(pTable);
        pQuery->bActive = true; // indicate query is now actively running
		pQuery->tableSize = -1; // don't yet know the download size
		pQuery->progressCount = 0;
        pQuery->startTime = gos_GetHiResTime();
        pQuery->lastUpdateTime = pQuery->startTime;
        return true;
    }
CLEANUP:
    if ( pTable )
    {
        wRelease( pTable );
    }
    pQuery->bActive = false;

    return false;
}


int GUNUpdateServer( void )
{
    // todo: report error that we did not manage to connect to the system
    return 0;
}



////////////////////////////////////////////////////////////////////////////////////////
//  GUNCloseQuery()
//  Purpose:
//       Close the specified query, stopping further updates and freeing the state 
//       managed by it.  This does nto free the actual GUNQuery structure, though it 
//       renders most of its values invalid ( row headers are NULL, so are the 
//       interface pointers when the function completes.
//  
//  Parameters:
//       [query] :
//  
//  Returns:  none
//  
//  Change Log
//       Author                 MMDDYY  Description
//       --------------------   ------  ------------------------------------------------
//       Michael Moore          082800  Initial Version
//  
//       
////////////////////////////////////////////////////////////////////////////////////////
void GUNCloseQuery( GUNQuery * pQuery, bool bIsError )
{
    if ( pQuery->bIsServerInfo && pQuery->pGameID && ServerBrowser::GetGameList())
    {
		Browse::GameList::Lock();
        ServerBrowser::GetGameList()->SetField( pQuery->pGameID, GAMELIST_KEY_SERVER_INFO_STATUS, bIsError ? 
            GAMELIST_VALUE_SERVER_INFO_STATUS_ERROR : GAMELIST_VALUE_SERVER_INFO_STATUS_SUCCESS );
		Browse::GameList::Unlock();
	}

    if ( pQuery->bIsFullUpdate )
    {
        if ( pQuery->tableSize > -1 )
        {
            // even if we only received one row, we have an accurate count of the table size.
            g_ServerListSize = pQuery->tableSize;
        }
    }


    if ( pQuery->pZMView )
    {
        pQuery->pZMView->CloseData(); // stop callbacks
    }
    pQuery->bActive = false;
    pQuery->progressCount = -1;
    pQuery->tableSize = -1;
    pQuery->lastUpdateTime = 0.0;
    pQuery->bIsServerInfo = false;
    pQuery->bIsFullUpdate = false;
    pQuery->startTime = 0.0; 

    GUNFreeHeaders( pQuery );

    if ( pQuery->pZMView )
    {
        wRelease(pQuery->pZMView);
        pQuery->pZMView = NULL;
    }

    if ( pQuery->pRowMgr )
    {
        wRelease(pQuery->pRowMgr);
        pQuery->pRowMgr = NULL;
    }

	if ( pQuery->pFilter )
	{
		free(pQuery->pFilter);
		pQuery->pFilter = NULL;
	}

	if ( pQuery->pGameID )
	{
		free( pQuery->pGameID );
		pQuery->pGameID = NULL;
	}


    // find the right head node for the query
    GUNQuery * pHeadNode = &g_playerQuery;
    if ( pQuery->queryType == GQT_SERVER )
    {
        pHeadNode = &g_serverQuery;
    }

	GUNQuery * pNode = pHeadNode;

    // find its place in the list.  If this query is the head of the list,
    // then pNode->pNext will be NULL on loop exit
    while ( pNode->pNext != pQuery )
    {
        if ( pNode->pNext == NULL )
        {
            break;
        }
        pNode = pNode->pNext;
    }

    // if non-null, then we found our place in the list
    // and this means we should remove ourselves from the
    // list and delete ourselves
    if ( pNode->pNext == pQuery )
    {
        pNode->pNext = pQuery->pNext;
        delete pQuery;
    }
    else
    {
        gosASSERT(pQuery == &g_serverQuery || pQuery == &g_playerQuery);

        // we are the head node -- g_playerQuery or g_serverQuery.
        // head nodes are not dynamically allocated, so we don't
        // free them.
    }

	// check for cancellation of server or player download status
    if ( pHeadNode == &g_serverQuery )
	{
		if ( g_serverQuery.pNext == NULL && g_serverQuery.bActive == false )
		{
			GUNNetStatus &= ~ServerBrowser::GNS_DOWNLOADING_SERVERS;
		}
	}
	else
	{
		if ( g_playerQuery.pNext == NULL && g_playerQuery.bActive == false )
		{
			GUNNetStatus &= ~ServerBrowser::GNS_DOWNLOADING_PLAYERS;
		}
	}



}


////////////////////////////////////////////////////////////////////////////////////////
//  GUNCloseAllQueries()
//  Purpose:
//       Close all player and server queries
//  
//  Parameters:
//       [void] :
//  
//  Returns:  none
//  
//  Change Log
//       Author                 MMDDYY  Description
//       --------------------   ------  ------------------------------------------------
//       Michael Moore          083000  Initial Version
//  
//       
////////////////////////////////////////////////////////////////////////////////////////
void GUNCloseAllQueries( void )
{
    while ( g_playerQuery.pNext )
    {
        GUNCloseQuery( g_playerQuery.pNext );
    }

    while ( g_serverQuery.pNext )
    {
        GUNCloseQuery( g_serverQuery.pNext );
    }

    GUNCloseQuery( &g_playerQuery );
    GUNCloseQuery( &g_serverQuery );
}


////////////////////////////////////////////////////////////////////////////////////////
//  GUNDestroyNetworking()
//  Purpose:
//       Called when app is destroying networking (in prep for exit)
//  
//  Parameters:
//       [void] :
//  
//  Returns:  none
//  
//  Change Log
//       Author                 MMDDYY  Description
//       --------------------   ------  ------------------------------------------------
//       Michael Moore          092600  Initial Version
//  
//       
////////////////////////////////////////////////////////////////////////////////////////
void _stdcall GUNDestroyNetworking( void )
{
    // any necessary cleanup that CloseGUN doesn't cover -- like singletons and such
   // ComDllManager::Destroy(); - 
    if ( Environment.ZoneMatchServerIP )
    {
        gos_Free(Environment.ZoneMatchServerIP);
        Environment.ZoneMatchServerIP = NULL;
    }
}


void CloseGUN( void )
{
    GUNStatus = GS_CLOSING;
	EnterCriticalSection( &EnumCriticalSection );

    GUNCloseAllQueries();

	if (g_pZMAsync)
	{
		g_pZMAsync->Stop();
		wRelease(g_pZMAsync);
		g_pZMAsync=NULL;
	}

    if ( g_pZMAsyncApp )
    {
        g_pZMAsyncApp->Stop();
        wRelease(g_pZMAsyncApp);
        g_pZMAsyncApp = 0;
    }


    if (g_pZMReg)
	{
		wRelease(g_pZMReg);
		g_pZMReg=NULL;
	}

    if (g_pZMApp)
	{
		wRelease(g_pZMApp);
		//asserting here because hash tables not being emptied
		//PAUSE(("Please continue IZoneMasterApplication asserting here because hash tables not being emptied\n"));
		g_pZMApp=NULL;
	}

    if (g_pZMBrowser) 
    {
        wRelease(g_pZMBrowser);
        g_pZMBrowser = NULL;
    }


    // mdm - shutdown ping services if they exist
    if ( ServerBrowser::GetGameList() )
    {
        ServerBrowser::GetGameList()->ShutdownPing();
    }

	LeaveCriticalSection( &EnumCriticalSection );

    WaitingForEnum = 0;
    LastEnumTime = 0;
    EnumNames = 0;
    ZoneMatchConnected = 0;
    GUNStatus = GS_CLOSED;
	GUNRegStatus = GRS_UNINITIALIZED;
	return;
};

int _stdcall InitGUNGames()
{
    GUNStatus = GS_INIT;

    return 0;
};


//
// Additional non driver related functions
//
//
//
//
//
//
//
void _stdcall CheckForInternet()
{
	// TODO:
	// Check for Internet using Millennium code
	//
};



/*
    This function tries to invoke DllRegisterServer on the
    dll indicated, thereby registering it as a COM server.
    The function returns false if the attempt fails for any
    reason.

    NOTE: This code was adapted from the regsvr32 sample
    code.
*/
bool GUNRegisterDLL( LPCTSTR dllPath )
{
    // describes name of function to call tor egister the dll
    const char * DLL_REG_FUNC = "DllRegisterServer";


	// Load the library.
	HINSTANCE hLib = LoadLibraryEx(dllPath, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);

	if (hLib < (HINSTANCE)HINSTANCE_ERROR) {
        goto Failure;
	}

	// Find the entry point.
	HRESULT (STDAPICALLTYPE * lpDllEntryPoint)(void);

	(FARPROC&)lpDllEntryPoint = GetProcAddress(hLib, DLL_REG_FUNC);

	if (lpDllEntryPoint == NULL) {
        goto Failure;
	}

	// Call the entry point.
	if (FAILED((*lpDllEntryPoint)())) {


        goto Failure;
	}

    FreeLibrary(hLib);
    return true;

Failure:
    // free library if we successfully opened it
    if ( hLib > (HINSTANCE)HINSTANCE_ERROR )
    {
		FreeLibrary(hLib);
    }

    return false;
}

/*
    This function tries to programmatically register
    the GUN dlls so that they can be accessed through
    COM methods.

    NOTE: This function assumes the location of the
    binary dlls for GUN are found in .\Assets\Binaries
    or in the working directory.
*/
bool GUNRegisterDLLs( void )
{
    // GUN Dlls that need to be registered
    char dllNames[2][20] = {
        "ZnMatch.dll",
        "ZoneNet.dll"
     };


    const char * REL_DLL_LOC = ".\\Assets\\Binaries";

    char dllPath[MAX_PATH];

    // mdm - assume they are already registered via setup program.
    return true;

    for (int i = 0; i < 2; i ++)
    {
        sprintf(dllPath, "%s\\%s", REL_DLL_LOC, dllNames[i]);

        if ( GUNRegisterDLL(dllPath) == false )
        {
            // this path is always <= rel_dll_loc path
            sprintf(dllPath, ".\\%s", dllNames[i]);

            // try current directory
            if ( GUNRegisterDLL(dllPath) == false )
            {
                SPEW( ( NULL, "Unable to register GUN dll: %s.\n", dllNames[i]) );
                return false;
            }
        }
    }
    return true;
}






bool CheckForZoneMatch()
{

    DiscoverZoneMatchSettings();

//telnet to port 80 www.microsoft.com, www.w3.org API InternetCheckConnection requires IE3.0
    if (hasTCP)
    {
		IZoneMatchApplication *pZMApp=NULL;
		//
		// Create the base application object to check if it is installed.
		//
	//
		// Create the base application object to check if it is installed.
		//
        if ( GUNRegisterDLLs() )
        {
		    CoCreateInstance(  CLSID_ZoneMatchApplication,
							       NULL, CLSCTX_INPROC_SERVER,
							       IID_IZoneMatchApplication,
							       (void**)&pZMApp );
        }
        else
        {
            // try anyway -- maybe they registered them somewhere else
            // and there just isn't an Assets\Binaries subfolder

		    CoCreateInstance( CLSID_ZoneMatchApplication,
							       NULL, CLSCTX_INPROC_SERVER,
							       IID_IZoneMatchApplication,
							       (void**)&pZMApp );

            SPEW( ( NULL, "Error registering GUN dlls") );
            if ( pZMApp )
            {
                SPEW( ( NULL, "Using Dlls registered elsewhere on the system.") );
            }
        }

		if (pZMApp)
		{
			wRelease(pZMApp);
            pZMApp = 0;
			hasZoneMatch=1;
            return true;
		}

    }
    return false;
};


bool __stdcall gos_NetSetAdvertItem( DWORD player, const char * Name , const char * Value)
{
    if ( Name == NULL || Value == NULL )
    {
        PAUSE(( "Advertise item has Name parameter: %s, and Value parameter: %s.\n",
            Name ? Name : "Null", 
            Value ? Value : "Null" ));
        return false;
    }

    ServerAdvertiser::AdvertiseItem(player, Name, Value);
	return 1;
}


/*
int LookupGameID( Browse::TableList * table, DWORD rowID )
{
    ItemID GetIDFromField( const char * key,
                           const char * val );

}
*/

void GUNUpdateTable( Browse::TableList * table,
                     const char * rowIDStr,
                     LPZONEMATCHROW pKeys,
                     LPZONEMATCHROW pValues )
{
    if (table->Exists(rowIDStr) == false )
    {
        if ( table->CreateEntry(rowIDStr) == false )
        {
            STOP(( "Unable to Create entry in TableList %d.", table ));
        }
    }

    // need ot have the same number of keys as values
    gosASSERT( pValues->Count() == pKeys->Count() );

    // now, set all key-value pairs found in row
    for (int j = 0; j < pValues->Count(); j++)
    {
        if ( pValues->Strings(j) )
        {
#ifdef PREPEND_PREFIX_TO_GAME_NAMES
			if (strcmp(pKeys->Strings(j), GAMELIST_KEY_SERVER_NAME) == 0)
			{
				char szGameName[256];
				strcpy(szGameName, "ZM:");
				strncat(szGameName, pValues->Strings(j), sizeof(szGameName));

				table->SetField( rowIDStr, pKeys->Strings(j),
								 szGameName );
			}
			else
#endif

            if ( !strcmp(pKeys->Strings(j), GAMELIST_KEY_GAME_TYPE) )
            {
                // convert the index to the string
                int index = atoi(pValues->Strings(j));
	            char szGameType[256];
	            Environment.GetSpecialGameData(0, index, sizeof(szGameType), szGameType);
	            table->SetField( rowIDStr, GAMELIST_KEY_GAME_TYPE,	szGameType);
            }
            else
            {
    			table->SetField( rowIDStr, pKeys->Strings(j), pValues->Strings(j) );
            }
        }
    }

	// set the protocol
	table->SetField( rowIDStr, GAMELIST_KEY_PROTOCOL, GAMELIST_VALUE_PROTOCOL_GUN);
}

void GUNUpdatePlayerCB(
		DWORD iRowID,
        IZoneMatchTableView::UPDATE_STATUS usRow,
        LPZONEMATCHROW pRow,
        LPVOID pContext )
{
    char rowIDStr[25];
    sprintf(rowIDStr, "%d", iRowID);

    GUNQuery * pQuery = (GUNQuery *) pContext;

    pQuery->lastUpdateTime = gos_GetHiResTime();

	if ( pQuery->queryStyle == GQS_ONESHOT )
	{
		// store the table size
        unsigned long tableSize;
		HRESULT hr = pQuery->pZMView->GetViewSize(&tableSize);

		if ( FAILED(hr) )
		{
			pQuery->tableSize = -1;
		}
        else
        {
            pQuery->tableSize = tableSize;
        }

		pQuery->progressCount++;
	}


    if ( usRow == IZoneMatchTableView::ROW_END )
    {
		// mark the status of the query for this server as completed successfully
		// if this is a server info query
        // not downloading anymore
        pQuery->bActive = false;
		pQuery->progressCount = pQuery->tableSize; // make sure they match exactly even if some rows were dropped.
        GUNCloseQuery( pQuery );

        return;
    }
    
	if ( !pQuery->pHeaders )
	{
		return;
	}

	if ( pQuery->queryStyle == GQS_ONESHOT )
	{
		pQuery->progressCount++;
	}
	
	EnterCriticalSection( &EnumCriticalSection );


    // game list was deleted - no updates matter
    // may not be necessary anymore, but a good safety measure
    if ( ServerBrowser::GetGameList() == NULL )
    {
        LeaveCriticalSection( &EnumCriticalSection );
        return;
    }

    Browse::PlayerList * pList;
    static Browse::PlayerList playerList;

    // load player into temp table so we can access its fields

	if ( pRow )
	{
		GUNUpdateTable( &playerList, rowIDStr, pQuery->pHeaders, pRow );
	}

    const char * serverRowID = playerList.GetDataField( rowIDStr, PLAYERLIST_KEY_SERVER_ROWID );

    if ( serverRowID == NULL )
    {
		// not in the update, so must be in the playerlist or we are
		// hosed.
        // should have been in the static player list, since
        // we just added it.

        PAUSE(( "ServerRowID not found in playerlist for row ID : %s.", rowIDStr ));
        goto CLEANUP;
        return;
    }

    //
    // Create a new Game entry, or update existing
    //
    if ( ServerBrowser::GetGameList()->Exists( serverRowID ) == false )
    {

        if ( usRow == IZoneMatchTableView::ROW_DELETED )
        {
            // removing a player whos server isn't found in our
            // list corresponds to receiving a deleted update for
            // a player we have never of before.  This could also
            // happen if we receive the delete server update before
            // all the delete player updates.  In either case,
            // this is probably ok and our response is to just
            // ignore it since we haven't got the player stored
            // anyway.
            goto CLEANUP;
        }

        // received player update for a server that we don't know of yet.
        // Create a minimal server entry and stick player in it.  If
        // we get the server update later we'll fill in the fields at
        // that time.
        if ( ServerBrowser::GetGameList()->CreateEntry( serverRowID ) == false )
        {
            STOP(( "Unable to create new game list entry for serverID %s.", serverRowID ));
            goto CLEANUP;
        }
        // install server browser handle since if an entry exists, code assumes this key is set too
    	ServerBrowser::GetGameList()->SetField( rowIDStr, GAMELIST_KEY_BROWSER_HANDLE, GUNServerBrowser::m_pGun->GetBrowserHandle());

    }

    // update player list for the server
    pList = ServerBrowser::GetGameList()->GetPlayerList( serverRowID );
    gosASSERT(pList);

    if ( usRow == IZoneMatchTableView::ROW_DELETED )
    {
        // remove player from server's list
        pList->Remove( rowIDStr );
    }
    else
    {
        GUNUpdateTable( pList, rowIDStr, pQuery->pHeaders, pRow );
    }

CLEANUP:
	LeaveCriticalSection( &EnumCriticalSection );

    return;
}


////////////////////////////////////////////////////////////////////////////////////////
//  QueryMarkRefresh()
//  Purpose:
//       Marks the game in the gamelist as having a refresh pending from the indicated 
//       query.
//  
//  Parameters:
//       [pQuery] :
//       [gameID] :
//  
//  Returns:  none
//  
//  Change Log
//       Author                 MMDDYY  Description
//       --------------------   ------  ------------------------------------------------
//       Michael Moore          100200  Initial Version
//  
//       
////////////////////////////////////////////////////////////////////////////////////////
void QueryMarkRefresh( GUNQuery * pQuery, const char * gameID )
{
    if ( !pQuery || !gameID )
    {
        PAUSE(( "QueryMarkRefresh invoked with NULL query or gameID string.\n" ));
        return;
    }

    Browse::GameList * pGameList = ServerBrowser::GetGameList();

    if ( pGameList == NULL )
    {
        PAUSE(( "QueryMarkRefresh invoked when game list was NULL.\n" ));
        return;
    }

    // create a string that represents the hex address of this query.  THis is used
    // to mark queries for cleanup later if they don't get updates because they are
    // no longer hosted.
    char hexAddress[64];
    sprintf(hexAddress, "%0x", pQuery);

    pGameList->SetField(gameID, GAMELIST_KEY_REFRESH_PENDING, hexAddress);
}



////////////////////////////////////////////////////////////////////////////////////////
//  QueryMarkAllRefresh()
//  Purpose:
//       Mark all GUN games in the list as being refreshed by the indicated query.  
//       Uses QueryMarkRefresh()
//  
//  Parameters:
//       [pQuery] :
//  
//  Returns:  none
//  
//  Change Log
//       Author                 MMDDYY  Description
//       --------------------   ------  ------------------------------------------------
//       Michael Moore          100200  Initial Version
//  
//       
////////////////////////////////////////////////////////////////////////////////////////
void QueryMarkAllRefresh( GUNQuery * pQuery )
{
    Browse::GameList * pGameList = ServerBrowser::GetGameList();

    if ( !pGameList )
    {
        return;
    }

	Browse::IndexedTableView * pIndexView = pGameList->GetIndexedView();
	gosASSERT(pIndexView);
	int numGames = pIndexView->GetCount();

    for ( int i = 0; i < numGames; i++ )
    {
        // get the gameID
        char * pGameID;

        if ( pIndexView->GetIndex( i, &pGameID) == false )
        {
            PAUSE(( "GetIndex() failed for gamelist index %d.\n", i ));
            continue;
        }

        // find out the protocol type for the game
        const char * pProtocol = pGameList->GetDataField( pGameID, GAMELIST_KEY_PROTOCOL );

        if ( pProtocol == NULL || strcmp(pProtocol, GAMELIST_VALUE_PROTOCOL_GUN) )
        {
            // no such game or no protocol identified for it.
            continue;
        }

        QueryMarkRefresh( pQuery, pGameID );
    }
}



////////////////////////////////////////////////////////////////////////////////////////
//  CleanUpRefresh()
//  Purpose:
//       Only call this when the query has received its ROW_END.
//       Go through the game list and find any entries that are marked as being
//       refreshed by this query in the GAMELIST_KEY_REFRESH_PENDING field.  Any such
//       entry found is assumed to be a game that was not refreshed and is deleted
//       (because as updates are received the field is cleared, and this function 
//       is meant to only be called at the receipt of a ROW_END for a query)
//  
//  Parameters:
//       [pQuery] : query to clean up after
//  
//  Returns:  none
//  
//  Change Log
//       Author                 MMDDYY  Description
//       --------------------   ------  ------------------------------------------------
//       Michael Moore          100200  Initial Version
//  
//       
////////////////////////////////////////////////////////////////////////////////////////
void CleanUpRefresh( GUNQuery * pQuery )
{
    Browse::GameList * pGameList = ServerBrowser::GetGameList();
    if ( !pGameList )
    {
        return;
    }

    Browse::IndexedTableView * pIndexView = pGameList->GetIndexedView();
    if ( !pIndexView )
    {
        return;
    }

    char hexAddress[64];
    sprintf(hexAddress, "%0x", pQuery);

    for ( int i = 0; i < pIndexView->GetCount(); i++) 
    {
        char * pGameID = NULL;

        if ( pIndexView->GetIndex( i, &pGameID ) == false )
        {
            continue;
        }

        const char * pProtocol = pGameList->GetDataField( pGameID, GAMELIST_KEY_PROTOCOL);
        if ( !pProtocol || strcmp(pProtocol, GAMELIST_VALUE_PROTOCOL_GUN) )
        {
            continue;
        }

        const char * pRefreshPending = pGameList->GetDataField( pGameID,
            GAMELIST_KEY_REFRESH_PENDING);
        if ( pRefreshPending )
        {
            if ( !strcmp(hexAddress, pRefreshPending) )
            {
                // match -- remove this item
                pGameList->Remove( pGameID );
                i--; // list just got one shorter -- stay on same index for next iteration
            } 
        }
    }
}

void
GUNUpdateServerCB(
		DWORD iRowID,
        IZoneMatchTableView::UPDATE_STATUS usRow,
        LPZONEMATCHROW pRow,
        LPVOID pContext )
{
    const char * serverName = NULL;
    ListOfGames* pThis=NULL;
	GUID	guidInstance;
    const char * password = NULL;
    int indexCount = 0;
	ListOfGames* pCurrent=NULL;
    GUNQuery * pQuery = (GUNQuery *) pContext;
    char rowIDStr[20];

    sprintf(rowIDStr, "%d", iRowID);

    pQuery->lastUpdateTime = gos_GetHiResTime(); // note last time we got an update for this query

	if ( pQuery->queryStyle == GQS_ONESHOT )
	{
		// store the table size
        unsigned long tableSize;
		HRESULT hr = pQuery->pZMView->GetViewSize(&tableSize);

		if ( FAILED(hr) )
		{
			pQuery->tableSize = -1;
		}
        else
        {
            pQuery->tableSize = tableSize;
        }


        if ( usRow != IZoneMatchTableView::ROW_END )
        {
    		pQuery->progressCount++;
        }
	}

    if ( usRow == IZoneMatchTableView::ROW_END )
    {
        // not downloading anymore - only get this for GQS_ONESHOT views
        pQuery->bActive = false;
		pQuery->progressCount = pQuery->tableSize; // count won't necessarily match exactly

        CleanUpRefresh( pQuery ); // only call this on receipt of ROW_END
        GUNCloseQuery( pQuery );

        return;
    }

	if (!pQuery->pHeaders)
    {
		return;
    }

	EnterCriticalSection( &EnumCriticalSection );

    // game list was deleted - no updates matter
    // may not be necessary anymore, but a good safety measure
    if ( ServerBrowser::GetGameList() == NULL )
    {
        LeaveCriticalSection( &EnumCriticalSection );
        return;
    }

    if ( usRow == IZoneMatchTableView::ROW_DELETED )
    {
        if ( ServerBrowser::GetGameList()->Remove( rowIDStr ) == false )
        {
            // something went wrong removing it -- perhaps the game
            // didn't exist in the first place?
        }
        // server is removed, no other work to do
        goto CLEANUP;
    }

//
// Create a new Game entry, or update existing
//
    GUNUpdateTable( ServerBrowser::GetGameList(), rowIDStr,
        pQuery->pHeaders, pRow );

	ServerBrowser::GetGameList()->SetField( rowIDStr, GAMELIST_KEY_BROWSER_HANDLE, GUNServerBrowser::m_pGun->GetBrowserHandle());

    if ( ServerBrowser::GetGameList()->Exists( rowIDStr ) )
    {
        goto CLEANUP;
    }

    // mdm - First determine the name of the game server
    serverName = ServerBrowser::GetGameList()->GetDataField( rowIDStr, GAMELIST_KEY_SERVER_NAME);

    pThis=(ListOfGames*)malloc( sizeof(ListOfGames) + strlen(serverName) );

//
// Use IP address as name for now
//
	memset( &pThis->SessionDescriptor, 0, sizeof(pThis->SessionDescriptor) );
    strcpy( pThis->Name, serverName );


    if ( ServerBrowser::GetGameList()->GetGUID( rowIDStr, guidInstance ) == false )
    {
        // no such GUID -- shouldn't happen
        STOP(( "Unable to locate GUID data in UpdateServerCB() for serverID %s.", rowIDStr ));
        // handle error better here
    }

    // find the column index for the password header,
    // which tells us if the game is password-protected
    password = ServerBrowser::GetGameList()->GetDataField( rowIDStr,
        GAMELIST_KEY_PASSWORD_PROTECTED );

    if ( password )
    {
        if (!stricmp(password, "yes"))
        {
            pThis->SessionDescriptor.dwFlags |= DPSESSION_PASSWORDREQUIRED;
        }
    }

	memcpy(&pThis->SessionDescriptor.guidInstance,&guidInstance,sizeof(GUID));


//
// Initialize rest of structure
//
	pThis->pPlayers=0;
	pThis->pNext=0;
//
// Add it to the end of the current list
//
    indexCount = 0;
	pCurrent=EnumNames;

	if( pCurrent )
	{
		while( pCurrent->pNext )
		{
			pCurrent=pCurrent->pNext;
            indexCount++;
		}

		pCurrent->pNext=pThis;
        indexCount++;
	}
	else
	{
		EnumNames=pThis;
	}



CLEANUP:
    if ( usRow == IZoneMatchTableView::ROW_UPDATE )
    {
        // mark that we received an update for this server
        if ( ServerBrowser::GetGameList() )
        {
            ServerBrowser::GetGameList()->SetField( rowIDStr, GAMELIST_KEY_REFRESH_PENDING, 
                GAMELIST_VALUE_REFRESH_PENDING_FALSE);
        }
    }
            
	WaitingForEnum=0;
    LastEnumTime = gos_GetHiResTime();
	LeaveCriticalSection( &EnumCriticalSection );

};














//========================================================================
//========================================================================
bool __stdcall InitGUNServerAdvertiser(void)
{
	if (!hasZoneMatch)
    {
        if ( !hasTCP )
        {
//            STOP(( "Failed to initialize TCP/IP for ZoneMatch.  You must have the TCP/IP protocol installed for ZoneMatch to work." ));
        }
        else
        {
//    		STOP(( "Failed to initialize ZoneMatch.  One or more components may be missing or improperly installed.  Please reinstall the application." ));
        }
        return false;
    }

	GUNServerAdvertiser *pgun;
	pgun = new GUNServerAdvertiser();

	if (pgun == NULL)
		return NULL;

	pgun->Initialize();
	pgun->Register();

	return true;
}

void GUNServerAdvertiser::FreeInterfaces( void )
{
    EnterCriticalSection( &EnumCriticalSection );

    if ( g_pZMAsyncApp )
    {
        g_pZMAsyncApp->Stop();
        wRelease(g_pZMAsyncApp);
        g_pZMAsyncApp = 0;
    }

    if (g_pZMApp)
	{
	
		g_pZMApp->Close();
		wRelease(g_pZMApp);
		//asserting here because hash tables not being emptied
		//PAUSE(("Please continue IZoneMasterApplication asserting here because hash tables not being emptied\n"));
		g_pZMApp=NULL;
	}

	//this must be after the app close because
	//app close is what waits for network threads to shutdown
	//reg object has outstanding resolve host name when
	//you cant reach zonemaster server and this must
	//come back before shutting down registration object
	if (g_pZMReg)
	{
		wRelease(g_pZMReg);
		g_pZMReg=NULL;
	}

	GUNRegStatus = GRS_UNINITIALIZED;

    LeaveCriticalSection( &EnumCriticalSection );
}


//========================================================================
//========================================================================
bool __stdcall GUNServerAdvertiser::Release(void)
{
	Unregister();

    FreeInterfaces();

    if ( m_pAppDesc != NULL )
    {
        FREE_PTR( m_pAppDesc->pszProductName );
        FREE_PTR( m_pAppDesc->pszVersion );
        FREE_PTR( m_pAppDesc->pszServerName );
        delete m_pAppDesc;
        m_pAppDesc = NULL;
    }

    // mdm - shutdown ping services if they exist
    if ( ServerBrowser::GetGameList() )
    {
        ServerBrowser::GetGameList()->ShutdownPing();
    }

	delete this;
	return true;
}



//========================================================================
bool __stdcall GUNServerAdvertiser::Initialize(void)
{
    m_AppQueryPort = (unsigned short) Environment.ZoneAdvertisePort;
	m_pAppDesc=NULL;
	m_LastAdvertiseTime = 0;
	m_AdvertiseCheckFreq = (double) ADVERTISE_CHECK_FREQUENCY;
    m_bInReregister = false;
    strcpy(m_szFlags, "0");
	gos_ConnectZoneMatch( NULL, 0 );
	return true;
}

//========================================================================
//========================================================================
bool __stdcall GUNServerAdvertiser::Disconnect(void)
{
    CloseGUN();
	return true;
}

//========================================================================
//========================================================================
const char *__stdcall GUNServerAdvertiser::GetDescription(void)
{
	return "GUN";
}

//========================================================================
//========================================================================
int __stdcall GUNServerAdvertiser::GetStatus(void)
{
	return true;
}

//========================================================================
//========================================================================
bool __stdcall GUNServerAdvertiser::Synchronize(void)
{
    if ( m_pAppDesc == NULL )
    {
        // nothing is being registered, so we have no work to do
        return true;
    }

	if ( g_pZMAsyncApp )
	{
		IZoneAsync::STATUS Status;
		g_pZMAsyncApp->GetStatus( &Status );
		switch (Status)
		{
		case IZoneAsync::STATUS_FAILED:
        case IZoneAsync::STATUS_COMPLETED:
        case IZoneAsync::STATUS_CANCELLED:
			GUNRegStatus = GRS_FAILURE;
			break;
		case IZoneAsync::STATUS_RUNNING:
			GUNRegStatus = GRS_SUCCESS;
			break;
		}
	}

    if ( GUNRegStatus == GRS_FAILURE && m_bInReregister == false)
    {
        // try to reconnect and re-register if appropriate amount of time since last
        // attempt has gone by
        if ( gos_GetHiResTime() - m_LastAdvertiseTime > m_AdvertiseCheckFreq )
        {
            m_bInReregister = true; // multithread safe because m_AdvertiseCheckFreq is many seconds long

            SPEW(( GUN_SPEW_ID, "Trying to reconnect to query server for registration @ %f seconds .\n", gos_GetHiResTime() ));
            // try again!

            // make a whole new zonematch application object.  This object
            // receives a copy of the current state of players & item values
            // for g_pZMApp.  Then, if the registration attempt succeeds we
            // copy the values in the copy back to g_pZMApp, which has since
            // been destroyed and recreated by RegisterGame().

            // mdm - need to lock enum critical section here to avoid g_pZMApp from disappearing
        	EnterCriticalSection( &EnumCriticalSection );

            IZoneMatchApplication * pCopyApp = g_pZMApp;
            if ( pCopyApp )
            {
                pCopyApp->AddRef(); // keep it around even though 
            }

            RegisterGame( m_pAppDesc );

            if ( g_pZMApp && pCopyApp)
            {
                PushAdvertItems( g_pZMApp, pCopyApp );
            }
            if ( pCopyApp ) 
            {
                pCopyApp->Release();
                pCopyApp = NULL;
            }

            // mdm - need to free enum critical section here 
        	LeaveCriticalSection( &EnumCriticalSection );

            m_bInReregister = false;
        }
    }

	return true;
}


////////////////////////////////////////////////////////////////////////////////////////
//  GUNServerAdvertiser::AppEnumItemsCB()
//  Purpose:
//       Static callback used to enumerate itemvalues in IZoneMatchApplication objects
//  
//  Parameters:
//       [dwItemID] :        IZoneMatchApplication * pDestApp;

//       [pszKey] :
//       [pszValue] :
//       [pContext] :
//  
//  Returns:  BOOL WINAPI
//  
//  Change Log
//       Author                 MMDDYY  Description
//       --------------------   ------  ------------------------------------------------
//       Michael Moore          100100  Initial Version
//  
//       
////////////////////////////////////////////////////////////////////////////////////////
BOOL WINAPI GUNServerAdvertiser::AppEnumItemsCB( DWORD dwItemID,
                                                 LPCSTR pszKey,
                                                 LPCSTR pszValue,
                                                 LPVOID pContext )
{
    CBData * pCBData = (CBData *) pContext;

    pCBData->pDestApp->SetItemValue( dwItemID, pszKey, pszValue );

    return TRUE;
}


////////////////////////////////////////////////////////////////////////////////////////
//  GUNServerAdvertiser::AppEnumPlayersCB()
//  Purpose:
//       Static callback used to enumerate players in IZoneMatchApplication objects.
//  
//  Parameters:
//       [dwItemID] :
//       [pszPlayerName] :
//       [pContext] :
//  
//  Returns:  BOOL WINAPI
//  
//  Change Log
//       Author                 MMDDYY  Description
//       --------------------   ------  ------------------------------------------------
//       Michael Moore          100100  Initial Version
//  
//       
////////////////////////////////////////////////////////////////////////////////////////
BOOL WINAPI GUNServerAdvertiser::AppEnumPlayersCB( DWORD dwItemID,
                                                   LPCSTR pszPlayerName,
                                                   LPVOID pContext )
{
    CBData * pCBData = (CBData *) pContext;

    pCBData->pDestApp->AddPlayer( dwItemID, pszPlayerName );
    
    pCBData->pFromApp->EnumItemValues( dwItemID, AppEnumItemsCB, pContext );

    return TRUE;
}

////////////////////////////////////////////////////////////////////////////////////////
//  GUNServerAdvertiser::PushAdvertItems()
//  Purpose:
//       Push game state from pFromApp to pDestApp
//  
//  Parameters:
//       [pDestApp] :
//       [pFromApp] :
//  
//  Returns:  none
//  
//  Change Log
//       Author                 MMDDYY  Description
//       --------------------   ------  ------------------------------------------------
//       Michael Moore          100100  Initial Version
//  
//       
////////////////////////////////////////////////////////////////////////////////////////
void GUNServerAdvertiser::PushAdvertItems( IZoneMatchApplication * pDestApp, IZoneMatchApplication * pFromApp )
{
    CBData data;
    data.pDestApp = pDestApp;
    data.pFromApp = pFromApp;

    // enumerate players - this doesn't cover server player
    pFromApp->EnumPlayers( AppEnumPlayersCB, (LPVOID) &data );

    // enumerate server properties
    pFromApp->EnumItemValues( 0, AppEnumItemsCB, (LPVOID) &data );
}


//========================================================================
//========================================================================
bool GUNServerAdvertiser::RegisterGame( IZoneMatchApplication::APPDESCRIPTION * pAppDesc )
{
    HRESULT hr;
    DWORD dwFlags;
    bool retval = false;

    FreeInterfaces(); // if we have interfaces, free them

   	EnterCriticalSection( &EnumCriticalSection );

	gosASSERT(!g_pZMApp);
	gosASSERT(!g_pZMReg);
	gosASSERT(!g_pZMAsyncApp);


	GUNRegStatus = GRS_INPROGRESS;

    m_LastAdvertiseTime = gos_GetHiResTime();

	//
	// Create game application information holder
	//
	wCoCreateInstance( CLSID_ZoneMatchApplication,
						   NULL, CLSCTX_INPROC_SERVER,
						   IID_IZoneMatchApplication,
						   (void**)&g_pZMApp );

    m_AppQueryPort = (unsigned short) Environment.ZoneAdvertisePort;
	do 
	{
		//
		// Try to re-register with a different port number until we succeed here
		// or exhause our MAX_REGISTER_TRIES
		//
		SPEW(( GUN_SPEW_ID, "===========> GUN Register: Trying port %d.\n", m_AppQueryPort ));

		hr = g_pZMApp->InitApplication( pAppDesc,
    									NULL,  // Defaults to first local addr
	    								m_AppQueryPort );   // Defaults to Standard query port

		if( FAILED(hr) )
		{
            m_AppQueryPort++;
		}
	} while ( FAILED(hr) && (m_AppQueryPort < Environment.ZoneAdvertisePort + MAX_REGISTER_TRIES) );

	if ( FAILED(hr) )
	{
		SPEW(( GUN_SPEW_ID, "Failed to call InitApplication while registering game. Error Result: %d\n", hr ));
		// unable to register under GUN
		GUNRegStatus = GRS_FAILURE;
		GUNLastError = hr;
        goto CLEANUP;
	}


	//
	// Create registration object here rather than later to save which server we want to connect to
	//
	hr = g_pZMApp->CreateRegistration( NULL, IID_IZoneMatchRegistration,
					 (LPVOID*)&g_pZMReg );
	if( FAILED(hr) )
    {
		// unable to register under GUN
		SPEW(( GUN_SPEW_ID, "Failed to call CreateRegistration while registering game. Error Result: %d\n", hr ));
		GUNRegStatus = GRS_FAILURE;
		GUNLastError = hr;
        goto CLEANUP;
    }


	//
	// set register server but don't start until client wants to connect
	// master server IP address should be
	//

	hr = g_pZMReg->InitRegistration( Environment.ZoneMatchServerIP, MASTER_SERVER_PORT );
	if( FAILED(hr) )
    {
		// unable to register under GUN
		SPEW(( GUN_SPEW_ID, "Failed to call InitRegistration while registering game. Error Result: %d\n", hr ));
		GUNRegStatus = GRS_FAILURE;
		GUNLastError = hr;
        goto CLEANUP;
    }


	//
	// Add dummy item data
	//
	//gos_NetSetAdvertItem(0,GAMELIST_KEY_MAP_TYPE,"-");
	SetItemValue(0,GAMELIST_KEY_MAP_TYPE,"-");

    // 
    // Advertise the game's dwFlags
    //
    //gos_NetSetAdvertItem(0, GAMELIST_KEY_FLAGS, m_szFlags);
	SetItemValue(0, GAMELIST_KEY_FLAGS, m_szFlags);

    // 
    // Default game type index is 0
    //gos_NetSetAdvertItem(0, GAMELIST_KEY_GAME_TYPE, "0");
	SetItemValue(0, GAMELIST_KEY_GAME_TYPE, "0");

	//
	// Add server player if not dedicated
	//
    dwFlags = atoi(m_szFlags);
	if ((dwFlags & NETFLAGS_DEDICATED_FLAG)==0)
    {
	    hr = g_pZMApp->AddPlayer(MyDirectPlayID, m_PlayerName);
	    if( FAILED(hr) )
	    {
		    // unable to add a player to the application - non-fatal but shouldn't happen
		    // so we pause.
		    PAUSE(( "FAILED (0x%x - %s) - IZoneMasterApplication::AddPlayer\n ",hr,ErrorNumberToMessage(hr)));
	    }
    }

    //
	// Don't block here start connect to server
	// user interface will poll to see status of connection
	//
	wQueryInterface(g_pZMReg, IID_IZoneAsync, (PVOID*)&g_pZMAsyncApp);
	
	hr = g_pZMAsyncApp->Start( NULL);
	if( hr != ZT_E_PENDING && hr != S_OK )
		PAUSE(( "FAILED (0x%x - %s) - IZoneMasterRegistration::Start\n",hr,ErrorNumberToMessage(hr)));


    // stop browsing for game servers - mdm
    if ( g_pZMAsync )
    {
        g_pZMAsync->Stop();
    }

	//
	// Return that status is ok, it is actually undefined because we don't know if we have connected
	// but we will send an event for this later
	//
    retval = true;

CLEANUP:

    LeaveCriticalSection( &EnumCriticalSection );
    return retval;

}

//========================================================================
//========================================================================
bool __stdcall GUNServerAdvertiser::CreateGame(const char *szGameName, const char *szPlayerName, int MaxPlayers, const char *szPassword, const GUID &guidInstance, DWORD dwFlags)
{
	//
    // Initialize the Application object now that we are acting as a server.
    //
    if ( szPlayerName )
    {
        gosASSERT( strlen(szPlayerName) < MAX_PLAYER_NAME );
        strncpy(m_PlayerName, szPlayerName, MAX_PLAYER_NAME);
        m_PlayerName[MAX_PLAYER_NAME]=0;
    }
    else
    {
        m_PlayerName[0] = NULL;
    }

    m_dwFlags = dwFlags;

    // free the old appdesc if there is one.
    if ( m_pAppDesc != NULL )
    {
        FREE_PTR( m_pAppDesc->pszProductName );
        FREE_PTR( m_pAppDesc->pszVersion );
        FREE_PTR( m_pAppDesc->pszServerName );
        delete m_pAppDesc;
        m_pAppDesc = NULL;
    }

	m_pAppDesc = new IZoneMatchApplication::APPDESCRIPTION;

	//
	// Hack must get the session description again because open doesn't populate the guidInstance variable
	// which we need for master server send
	//
	DPSESSIONDESC2 *pSession;
	DWORD dwSize;
	AllowFail=1;
	wGetSessionDesc( dplay4, NULL, &dwSize);
	if (dwSize == 0)
		return FALSE;
	AllowFail=0;
	pSession = (DPSESSIONDESC2 *)gos_Malloc(dwSize);
	wGetSessionDesc( dplay4, pSession, &dwSize);

	CopyMemory( &m_pAppDesc->guidApplication, 
			&Environment.GameGUID, sizeof(GUID) );
	m_pAppDesc->pszProductName = _strdup(Environment.applicationName);
    m_pAppDesc->pszModName = "Standard";
	m_pAppDesc->pszVersion = _strdup(Environment.version);
	memcpy( &m_pAppDesc->guidInstanceID,&pSession->guidInstance,sizeof(GUID) );
    m_pAppDesc->wAppServerPort = (unsigned short) GetDirectPlayPort(); // returns 0 if not in registry - which is interpreted to mean no port is being used..
    if ( !m_pAppDesc->wAppServerPort )
    {
        // mdm - GUN must use special translation from 1 to 0 for port number specification until
        // new DLLs are available.  The 1 value will be translated into a port number of zero
        // when you go to join a gun game.
        m_pAppDesc->wAppServerPort = 1;
    }

	m_pAppDesc->pszServerName = _strdup(szGameName);
	m_pAppDesc->dwMaxPlayers = MaxPlayers;
	if (szPassword && szPassword[0] != NULL)
    	m_pAppDesc->bPasswordProtected = TRUE;
	else
		m_pAppDesc->bPasswordProtected = FALSE;

    // store the flags string for this game
	sprintf(m_szFlags, "%u", pSession->dwUser1); 

	gos_Free(pSession);

    // call the internal create game that uses the appdesc we just created
    return RegisterGame( m_pAppDesc );
}

//========================================================================
//========================================================================
bool __stdcall GUNServerAdvertiser::CreatePlayer(DWORD dwItemID, const char *szPlayerName, bool bBot)
{
    //
    // If we are running using ZoneTech Add the player to the list of advertisted players
    // Not done in AddPlayerToGame because that API can be called to reenum many times
    //
	if ((Connected == NP_ZONEMATCH ) && IAmTheServer)
	{
		gosASSERT(g_pZMApp);
		
		g_pZMApp->AddPlayer(dwItemID, szPlayerName);
	}

	return true;
}

//========================================================================
//========================================================================
bool __stdcall GUNServerAdvertiser::RemovePlayer(DWORD dwItemID, const char *szPlayerName, bool bBot)
{
    if ((Connected == NP_ZONEMATCH) && IAmTheServer)
    {
        gosASSERT(g_pZMApp);
        g_pZMApp->DeletePlayer(dwItemID);
    }
	return true;
}


//========================================================================
//========================================================================
bool __stdcall GUNServerAdvertiser::SetFlags(DWORD dwFlags)
{
    // store the flags string for this game
    sprintf(m_szFlags, "%u", dwFlags);

    SetItemValue( 0, GAMELIST_KEY_FLAGS, m_szFlags);

	return true;
}

//========================================================================
//========================================================================
bool __stdcall GUNServerAdvertiser::SetItemValue(DWORD player, const char * Name , const char * Value)
{
	HRESULT hr=E_FAIL;

    if (Connected == NP_ZONEMATCH && IAmTheServer && g_pZMApp)
	{
		gosASSERT(g_pZMApp);
		gosASSERT(IAmTheServer==TRUE);


        if (!strcmp(Name, GAMELIST_KEY_GAME_TYPE))
	    {
		    //
		    // find the index in the list
		    //
		    int index;
		    char szGameType[256];

		    for (index=0 ; true ; index++)
		    {
			    Environment.GetSpecialGameData(0, index, sizeof(szGameType), szGameType);
			    if (!_mbscmp(szGameType, Value))
				    break;
			    if (szGameType[0]==0)
				    break;
		    }
		    if (szGameType[0]==0)
		    {
			    for (index=0 ; true ; index++)
			    {
				    Environment.GetSpecialGameData(0, index, sizeof(szGameType), szGameType);
				    if (!_mbsicmp(szGameType, Value))
					    break;
				    if (szGameType[0]==0)
					    break;
			    }
			    if (szGameType[0]==0)
				    index--;
		    }

            char ival[32];
            sprintf(ival, "%u", index);
            hr = g_pZMApp->SetItemValue(player, Name, ival);
	    }
        else
        {
    		hr = g_pZMApp->SetItemValue(player,Name,Value);
        }


		if (FAILED(hr))
		{
            if ( hr == ZT_E_MATCH_NO_PLAYER )
            {
                // mdm - this error is currently occuring sometimes in MW4 game launch.
                // For now, we just return false to indicate the set item failed.
                return false;
            }
            else
            {
                PAUSE(( "FAILED (0x%x - %s) - IZoneMasterApplication::SetItem(%d,%s,%s)\n",hr,ErrorNumberToMessage(hr),player, Name ? Name : "NULL", Value ? Value : "NULL"));
            }
			return false;
		}
	}

	return true;
}



//========================================================================
// Determines the address of the zone server by
// examining the registry key found in the local
// application hive.  If the key is not found,
// it is set to a default value as specified
// in the constant ZONE_SERVER_DEFAULT_ADDRESS.
//========================================================================
void DiscoverZoneMatchSettings( void )
{
	// find zone address in registry hive
	char  ServerIP[512];
    int   ServerPort = Environment.ZoneAdvertisePort;
	DWORD valSize = 512;

	ServerIP[0] = 0;

	gos_LoadDataFromRegistry( (char *) ZONE_SERVER_KEY, ServerIP, &valSize);
	if ( !valSize || ServerIP[0] == 0 )
	{
		// mdm - key not found or invalid -- install registry key for
		// zone server in application hive.
		gos_SaveStringToRegistry( (char *) ZONE_SERVER_KEY,
			(char *) ZONE_SERVER_DEFAULT_ADDRESS,
			strlen(ZONE_SERVER_DEFAULT_ADDRESS)+1);
    }

    if ( Environment.ZoneMatchServerIP )
    {
        gos_Free(Environment.ZoneMatchServerIP);
        Environment.ZoneMatchServerIP = NULL;
    }

	Environment.ZoneMatchServerIP = (char *) gos_Malloc( strlen(ServerIP) + 1);
	strcpy(Environment.ZoneMatchServerIP, ServerIP);

    // find the advertise port
    if ( !ServerPort )
    {
        valSize = sizeof(ServerPort);
	    gos_LoadDataFromRegistry( (char *) ZONE_ADVERTISE_PORT_KEY, &ServerPort, &valSize);
    }

    if ( !valSize || ServerPort <= 0 )
    {
        // use default app query port
        ServerPort = APP_QUERY_PORT;

        // write value to registry since there wasn't anything found
        valSize = sizeof(ServerPort);
        gos_SaveDataToRegistry( (char *) ZONE_ADVERTISE_PORT_KEY, &ServerPort, valSize);
    }


    Environment.ZoneAdvertisePort = ServerPort;
}


