CnC_Remastered_Collection

Command and Conquer: Red Alert
Log | Files | Refs | README | LICENSE

RAWOLAPI.CPP (65744B)


      1 //
      2 // Copyright 2020 Electronic Arts Inc.
      3 //
      4 // TiberianDawn.DLL and RedAlert.dll and corresponding source code is free 
      5 // software: you can redistribute it and/or modify it under the terms of 
      6 // the GNU General Public License as published by the Free Software Foundation, 
      7 // either version 3 of the License, or (at your option) any later version.
      8 
      9 // TiberianDawn.DLL and RedAlert.dll and corresponding source code is distributed 
     10 // in the hope that it will be useful, but with permitted additional restrictions 
     11 // under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT 
     12 // distributed with this program. You should have received a copy of the 
     13 // GNU General Public License along with permitted additional restrictions 
     14 // with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
     15 
     16 #ifdef WOLAPI_INTEGRATION
     17 
     18 //	rawolapi.cpp - Core WOLAPI interface functions stuff.
     19 //	Definitions for RAChatEventSink, RADownloadEventSink, RANetUtilEventSink.
     20 //	ajw 07/10/98
     21 
     22 #include "RAWolapi.h"
     23 #define IID_DEFINED
     24 #include "wolapi\wolapi_i.c"
     25 #include "WolapiOb.h"
     26 #include "WolStrng.h"
     27 #include "Wol_gsup.h"
     28 #include "wolapi\netutildefs.h"
     29 
     30 #include "WolDebug.h"
     31 
     32 bool operator<( const User& u1, const User& u2 );
     33 
     34 const char* Game_Registry_Key();
     35 
     36 //	The definitions of QueryInterface, AddRef, and Release are needed because we are not including
     37 //	files that ordinarily (under MSVC) would define these for us, as part of CComObjectRoot, I believe.
     38 //	This Watcom has no equivalent we can use, so we do it manually...
     39 
     40 //***********************************************************************************************
     41 RAChatEventSink::RAChatEventSink( WolapiObject* pOwnerIn ) : m_cRef( 0 ), 	//	init the reference count
     42 	bRequestServerListWait( false ),
     43 	pOwner( pOwnerIn ),
     44 	pServer( NULL ),
     45 	bConnected( false ),
     46 	hresRequestConnectionError( 0 ),
     47 	pChannelList( NULL ),
     48 	pUserList( NULL ),
     49 	pUserTail( NULL ),
     50 	szMotd( NULL ),
     51 	bJoined( false ),
     52 	bGotKickedTrigger( false ),
     53 	bIgnoreChannelLists( false ),
     54 	bRequestChannelListForLobbiesWait( false ),
     55 	pGameUserList( NULL ),
     56 	bRequestGameStartWait( false ),
     57 	pUserIPList( NULL ),
     58 	pUserIPListTail( NULL ),
     59 	iGameID( 0 )
     60 {
     61 }
     62 
     63 //***********************************************************************************************
     64 RAChatEventSink::~RAChatEventSink()
     65 {
     66 //	debugprint( "RAChatEventSink destructor\n" );
     67 	delete pServer;
     68 	delete [] szMotd;
     69 	DeleteChannelList();
     70 	DeleteUserList();
     71 	DeleteUserIPList();
     72 }
     73 
     74 //			Interface IUnknown Methods
     75 //***********************************************************************************************
     76 // QueryInterface
     77 //
     78 HRESULT __stdcall
     79 RAChatEventSink::QueryInterface(const IID& iid, void** ppv)
     80 {
     81 //	debugprint( "RAChatEventSink::QueryInterface\n" );
     82 	if ((iid == IID_IUnknown) ||(iid == IID_IChatEvent))
     83 	{
     84 		*ppv = (IChatEvent*)this;		//	Removed static_cast<> ajw
     85 	}
     86 	else
     87 	{
     88 		*ppv = NULL;
     89 		return E_NOINTERFACE;
     90 	}
     91 	((IUnknown*)(*ppv))->AddRef();		//	Removed reinterpret_cast<> ajw
     92 	return S_OK ;
     93 }
     94 
     95 //***********************************************************************************************
     96 // AddRef
     97 //
     98 ULONG __stdcall
     99 RAChatEventSink::AddRef()
    100 {
    101 //	debugprint( "RAChatEventSink::AddRef\n" );
    102 	return InterlockedIncrement(&m_cRef) ;
    103 }
    104 
    105 //***********************************************************************************************
    106 // Release
    107 //
    108 ULONG __stdcall
    109 RAChatEventSink::Release()
    110 {
    111 //	debugprint( "RAChatEventSink::Release\n" );
    112 	if (InterlockedDecrement(&m_cRef) == 0)
    113 	{
    114 		delete this ;
    115 		return 0 ;
    116 	}
    117 	return m_cRef;
    118 }
    119 
    120 //***********************************************************************************************
    121 //***********************************************************************************************
    122 STDMETHODIMP RAChatEventSink::OnServerList( HRESULT hRes, Server* pServerHead )
    123 {
    124 	//strcpy( szLadderServerHost, "games.westwood.com" );
    125 	//iLadderServerPort = 3840;
    126 	//strcpy( szGameResServerHost, "games.westwood.com" );
    127 
    128 //	debugprint( ">>> OnServerList got: %i ", hRes );
    129 	DebugChatDef( hRes );
    130 
    131 	if( pServer )
    132 	{
    133 		delete pServer;
    134 		pServer = NULL;
    135 	}
    136 
    137 	if( SUCCEEDED( hRes ) )
    138 	{
    139 		while( pServerHead )
    140 		{
    141 			//	Copy the first IRC Server to use in the RequestConnection() call.
    142 			if( !pServer && ( strcmp( (char*)pServerHead->connlabel, "IRC" ) == 0 ) )
    143 			{
    144 				pServer = new Server;
    145 				*pServer = *pServerHead;
    146 			}
    147 			else if( !*pOwner->szLadderServerHost && ( strcmp( (char*)pServerHead->connlabel, "LAD" ) == 0 ) )
    148 			{
    149 //				debugprint( "Scanning '%s'\n", (char*)pServerHead->conndata );
    150 				char* token;
    151 				token = strtok( (char*)pServerHead->conndata, ";" );
    152 				token = strtok( NULL, ";" );
    153 				strcpy( pOwner->szLadderServerHost, token );
    154 				token = strtok( NULL, ";" );
    155 				pOwner->iLadderServerPort = atoi( token );
    156 //				debugprint( "Ladder is at: %s, port %i\n", pOwner->szLadderServerHost, pOwner->iLadderServerPort );
    157 			}
    158 			else if( !*pOwner->szGameResServerHost1 && ( strcmp( (char*)pServerHead->connlabel, "GAM" ) == 0 ) )
    159 			{
    160 				//	This is the Red Alert game results port.
    161 				char* token;
    162 				token = strtok( (char*)pServerHead->conndata, ";" );
    163 				token = strtok( NULL, ";" );
    164 				strcpy( pOwner->szGameResServerHost1, token );
    165 				token = strtok( NULL, ";" );
    166 				pOwner->iGameResServerPort1 = atoi( token );
    167 //				debugprint( "GameRes is at: %s, port %i\n", pOwner->szGameResServerHost, pOwner->iGameResServerPort );
    168 			}
    169 			else if( !*pOwner->szGameResServerHost2 && ( strcmp( (char*)pServerHead->connlabel, "GAM" ) == 0 ) )
    170 			{
    171 				//	This is the Aftermath game results port.
    172 				char* token;
    173 				token = strtok( (char*)pServerHead->conndata, ";" );
    174 				token = strtok( NULL, ";" );
    175 				strcpy( pOwner->szGameResServerHost2, token );
    176 				token = strtok( NULL, ";" );
    177 				pOwner->iGameResServerPort2 = atoi( token );
    178 //				debugprint( "GameRes is at: %s, port %i\n", pOwner->szGameResServerHost, pOwner->iGameResServerPort );
    179 			}
    180 			pServerHead = pServerHead->next;
    181 		}
    182 	}
    183 
    184 	bRequestServerListWait = false;
    185 	return(S_OK);
    186 }
    187 
    188 //***********************************************************************************************
    189 STDMETHODIMP RAChatEventSink::OnPageSend( HRESULT hRes )
    190 {
    191 //	debugprint( ">>> OnPageSend got: %i ", hRes );
    192 	DebugChatDef( hRes );
    193 
    194 	if( hRes != CHAT_S_PAGE_NOTHERE && hRes != CHAT_S_PAGE_OFF && hRes != S_OK )
    195 		hRes = E_FAIL;
    196 
    197 	hresRequestPageResult = hRes;
    198 
    199 	bRequestPageWait = false;
    200 	return S_OK;
    201 }
    202 
    203 //***********************************************************************************************
    204 STDMETHODIMP RAChatEventSink::OnPaged( HRESULT, User* pUser, LPCSTR szMessage )
    205 {
    206 //	debugprint( ">>> OnPaged got: %s ", szMessage );
    207 
    208 	char* szPrint = new char[ strlen( (char*)pUser->name ) + strlen( szMessage ) + strlen( TXT_WOL_ONPAGE ) ];
    209 	sprintf( szPrint, TXT_WOL_ONPAGE, (char*)pUser->name, szMessage );
    210 	if( !pOwner->bInGame )
    211 		pOwner->PrintMessage( szPrint, WOLCOLORREMAP_PAGE );
    212 	else
    213 	{
    214 		Session.Messages.Add_Message( NULL, 0, szPrint, PCOLOR_GOLD, TPF_6PT_GRAD|TPF_USE_GRAD_PAL|TPF_FULLSHADOW, Rule.MessageDelay * TICKS_PER_MINUTE );
    215 		if( !pOwner->bFreezeExternalPager )
    216 			strcpy( pOwner->szExternalPager, (char*)pUser->name );
    217 		Map.Flag_To_Redraw(true);
    218 	}
    219 
    220 	delete [] szPrint;
    221 
    222 	Sound_Effect( WOLSOUND_ONPAGE );
    223 
    224 	return S_OK;
    225 }
    226 
    227 //***********************************************************************************************
    228 STDMETHODIMP RAChatEventSink::OnFind( HRESULT hRes, Channel* pChannel )
    229 {
    230 //	debugprint( ">>> OnFind got: %i ", hRes );
    231 	DebugChatDef( hRes );
    232 
    233 	if( hRes != CHAT_S_FIND_NOTHERE && hRes != CHAT_S_FIND_NOCHAN && hRes != CHAT_S_FIND_OFF && hRes != S_OK )
    234 		hRes = E_FAIL;
    235 
    236 	if( hRes == S_OK )
    237 		OnFindChannel = *pChannel;
    238 
    239 	hresRequestFindResult = hRes;
    240 
    241 	bRequestFindWait = false;
    242 	return S_OK;
    243 }
    244 
    245 //***********************************************************************************************
    246 STDMETHODIMP RAChatEventSink::OnLogout( HRESULT hRes, User* pUser )
    247 {
    248 //	debugprint( ">>> OnLogout got: " );
    249 	DebugChatDef( hRes );
    250 
    251 	if( hRes == S_OK )
    252 	{
    253 		//	Someone has been logged out by the chat server due to inactivity.
    254 		//	Fake a call to OnChannelLeave(), as the processing is identical.
    255 //		debugprint( "OnLogout calling OnChannelLeave for %s, owner=%i\n", (char*)pUser->name, ( pUser->flags & CHAT_USER_CHANNELOWNER ) );
    256 		OnChannelLeave( S_OK, NULL, pUser );
    257 	}
    258 
    259 	return S_OK;
    260 }
    261 
    262 //***********************************************************************************************
    263 STDMETHODIMP RAChatEventSink::OnBusy(HRESULT)
    264 {
    265 	return S_OK;
    266 }
    267 
    268 //***********************************************************************************************
    269 STDMETHODIMP RAChatEventSink::OnIdle(HRESULT)
    270 {
    271 	return S_OK;
    272 }
    273 
    274 //***********************************************************************************************
    275 STDMETHODIMP RAChatEventSink::OnConnection( HRESULT hRes, LPCSTR motd )
    276 {
    277 //	debugprint( ">>> OnConnection got: " );
    278 	DebugChatDef( hRes );
    279 
    280 	if( hRes == S_OK )
    281 	{
    282 
    283 		//	Prepare a new string for a modified version of motd.
    284 		szMotd = new char[ strlen( motd ) + 1 ];
    285 		//	Replace single line breaks with a space.
    286 		//	Replace double line breaks with double carriage returns.
    287 
    288 		bool	bJustDidBreak = false;
    289 		const char*	szIn = motd;
    290 		char*   szOut = szMotd;
    291 
    292 		while( *szIn )
    293 		{
    294 			if( *szIn == '\r' && *( szIn + 1 ) == '\n' )
    295 			{
    296 				if( !bJustDidBreak )
    297 				{
    298 					*szOut++ = ' ';
    299 					bJustDidBreak = true;
    300 				}
    301 				else
    302 				{
    303 					szOut--;
    304 					*szOut++ = '\r';
    305 					*szOut++ = '\r';
    306 					bJustDidBreak = false;
    307 //					debugprint( "^" );
    308 				}
    309 				szIn += 2;
    310 			}
    311 			else
    312 			{
    313 				*szOut++ = *szIn++;
    314 				bJustDidBreak = false;
    315 			}
    316 //			debugprint( "%c", *( szOut - 1 ) );
    317 		}
    318 		*szOut = 0;		//	Null-terminate.
    319 //		debugprint( "\n" );
    320 
    321 //		pOwner->PrintMessage( szMotd );
    322 
    323 		bConnected = true;
    324 	}
    325 	else
    326 	{
    327 		hresRequestConnectionError = hRes;
    328 
    329 		char szError[150];
    330 		ChatDefAsText( szError, hRes );
    331 		strcat( szError, " (Connect Error)" );
    332 //		debugprint( szError );
    333 	}
    334 
    335 	bRequestConnectionWait = false;
    336 	return S_OK;
    337 }
    338 
    339 //***********************************************************************************************
    340 STDMETHODIMP RAChatEventSink::OnChannelCreate( HRESULT hRes, Channel* )
    341 {
    342 //	debugprint( ">>> OnChannelCreate got: %i ", hRes );
    343 	DebugChatDef( hRes );
    344 
    345 //	if( bJoined )
    346 //	{
    347 //		WWMessageBox().Process( "RAChatEventSink::OnChannelCreate called when bJoined is true!" );
    348 //		Fatal( "RAChatEventSink::OnChannelCreate called when bJoined is true!" );
    349 //	}
    350 
    351 	if( SUCCEEDED( hRes ) )
    352 	{
    353 		bJoined = true;
    354 	}
    355 	bRequestChannelCreateWait = false;
    356 	return S_OK;
    357 }
    358 
    359 //***********************************************************************************************
    360 STDMETHODIMP RAChatEventSink::OnChannelModify(HRESULT, Channel *)
    361 {
    362 	return S_OK;
    363 }
    364 
    365 //***********************************************************************************************
    366 STDMETHODIMP RAChatEventSink::OnChannelJoin( HRESULT hRes, Channel* /*pChannel*/, User* pUser )
    367 {
    368 //	if( SUCCEEDED( hRes ) )
    369 //		debugprint( ">>> OnChannelJoin got: channel '%s', user '%s', %i ", (char*)pChannel->name, (char*)pUser->name, hRes );
    370 //	else
    371 //		debugprint( ">>> OnChannelJoin got: %i ", hRes );
    372 	DebugChatDef( hRes );
    373 
    374 //	//	Special case - ignore OnChannelJoin when waiting for a UserList.
    375 //	if( bRequestUserListWait )
    376 //	{
    377 //		debugprint( "bRequestUserListWait is true - ignoring join.\n" );
    378 //		return S_OK;
    379 //	}
    380 
    381 	hresRequestJoinResult = hRes;
    382 	if( SUCCEEDED( hRes ) )
    383 	{
    384 		if( pUser->flags & CHAT_USER_MYSELF )
    385 		{
    386 			bJoined = true;
    387 			bRequestChannelJoinWait = false;
    388 			if( pUserList )
    389 			{
    390 				//	Should never happen.
    391 //				debugprint( "pUserList should be NULL (as I am the joiner)!!! Deleting user list...\n" );
    392 				DeleteUserList();
    393 			}
    394 		}
    395 		else
    396 		{
    397 			if( pOwner->CurrentLevel == WOL_LEVEL_INGAMECHANNEL )
    398 			{
    399 				if( pOwner->pGSupDlg && 
    400 					( pOwner->pGSupDlg->bHostSayGo || pOwner->pGSupDlg->bHostWaitingForGoTrigger || 
    401 						pOwner->pGSupDlg->bExitForGameTrigger || iGameID ) )
    402 				{
    403 					//	A game has this moment entered the "must start" phase. We can ignore the fact that others are leaving the channel.
    404 //					debugprint( "Ignoring leave because game is starting.\n" );
    405 					return S_OK;
    406 				}
    407 			}
    408 			//	Add user to our current channel users list.
    409 			if( !pUserList )
    410 			{
    411 //				debugprint( "pUserList is null in OnChannelJoin - ignoring %s join... \n", (char*)pUser->name );
    412 				return S_OK;
    413 			}
    414 //			if( !pUserList )
    415 //			{
    416 //				//	There has to be at least one user there - you.
    417 //				debugprint( "pUserList is null in OnChannelJoin!!! users: %s\n", (char*)pUser->name );
    418 //				Fatal( "pUserList is null in OnChannelJoin!!!\n" );
    419 //			}
    420 			
    421 			User* pUserNew = new User;
    422 			*pUserNew = *pUser;
    423 			pUserNew->next = NULL;			//	(We don't want the value that was just copied!)
    424 
    425 			//	Insert user into list alphabetically.
    426 			InsertUserSorted( pUserNew );
    427 
    428 			//	Update the shown list.
    429 			pOwner->ListChannelUsers();
    430 
    431 			if( pOwner->CurrentLevel == WOL_LEVEL_INGAMECHANNEL )
    432 			{
    433 				_ASSERTE( pOwner->pGSupDlg );
    434 				pOwner->pGSupDlg->OnGuestJoin( pUser );
    435 
    436 				//	Ask for this player's IP address.
    437 				pOwner->RequestIPs( (char*)pUser->name );
    438 			}
    439 
    440 			if( pOwner->CurrentLevel == WOL_LEVEL_INGAMECHANNEL || pOwner->CurrentLevel == WOL_LEVEL_INLOBBY )
    441 			{
    442 				//	Request ladder results for new user.
    443 				pOwner->RequestLadders( (char*)pUser->name );
    444 			}
    445 		}
    446 	}
    447 	else
    448 	{
    449 		bRequestChannelJoinWait = false;
    450 	}
    451 
    452 	return S_OK;
    453 }
    454 
    455 //***********************************************************************************************
    456 void RAChatEventSink::InsertUserSorted( User* pUserNew )
    457 {
    458 	if( !pUserList )
    459 	{
    460 		pUserList = pUserNew;
    461 		pUserTail = pUserNew;
    462 	}
    463 	else
    464 	{
    465 		if( *pUserNew < *pUserList )
    466 		{
    467 			//	Insert user at beginning.
    468 			pUserNew->next = pUserList;
    469 			pUserList = pUserNew;
    470 		}
    471 		else
    472 		{
    473 			User* pUserCheck = pUserList;
    474 			User* pUserInsertAfter = NULL;
    475 			while( pUserCheck->next )
    476 			{
    477 				if( *pUserNew < *pUserCheck->next )
    478 				{
    479 					pUserInsertAfter = pUserCheck;
    480 					break;
    481 				}
    482 				pUserCheck = pUserCheck->next;
    483 			}
    484 			if( pUserInsertAfter )
    485 			{
    486 				pUserNew->next = pUserInsertAfter->next;
    487 				pUserInsertAfter->next = pUserNew;
    488 			}
    489 			else
    490 			{
    491 				//	Add user to end.
    492 				pUserTail->next = pUserNew;
    493 				pUserTail = pUserNew;
    494 			}
    495 		}
    496 	}
    497 }
    498 
    499 //***********************************************************************************************
    500 bool operator<( const User& u1, const User& u2 )
    501 {
    502 	if( u1.flags & CHAT_USER_CHANNELOWNER && !( u2.flags & CHAT_USER_CHANNELOWNER ) )
    503 		return true;
    504 	if( !( u1.flags & CHAT_USER_CHANNELOWNER ) && u2.flags & CHAT_USER_CHANNELOWNER )
    505 		return false;
    506 	if( u1.flags & CHAT_USER_VOICE && !( u2.flags & CHAT_USER_VOICE ) )
    507 		return true;
    508 	if( !( u1.flags & CHAT_USER_VOICE ) && u2.flags & CHAT_USER_VOICE )
    509 		return false;
    510 	return ( _stricmp( (char*)u1.name, (char*)u2.name ) < 0 );
    511 }
    512 
    513 //***********************************************************************************************
    514 STDMETHODIMP RAChatEventSink::OnChannelLeave( HRESULT hRes, Channel*, User* pUser )
    515 {
    516 	//	Note: This is also called directly from OnUserKick(), below, when someone is kicked from a channel.
    517 	//	Also now from OnLogout().
    518 
    519 //	debugprint( ">>> OnChannelLeave got %s: ", (char*)pUser->name );
    520 	DebugChatDef( hRes );
    521 
    522 //	//	Special case - ignore OnChannelLeave when waiting for a UserList.
    523 //	if( bRequestUserListWait )
    524 //	{
    525 //		debugprint( "bRequestUserListWait is true - ignoring leave.\n" );
    526 //		return S_OK;
    527 //	}
    528 
    529 	if( SUCCEEDED( hRes ) )
    530 	{
    531 		if( pUser->flags & CHAT_USER_MYSELF )
    532 		{
    533 			bJoined = false;
    534 			bRequestChannelLeaveWait = false;
    535 		}
    536 		else
    537 		{
    538 			//	Remove user from our current channel users list.
    539 			if( !pUserList )
    540 			{
    541 //				debugprint( "pUserList is null in OnChannelLeave - ignoring %s leave... \n", (char*)pUser->name );
    542 				return S_OK;
    543 			}
    544 			if( pOwner->CurrentLevel == WOL_LEVEL_INGAMECHANNEL )
    545 			{
    546 				if( pOwner->pGSupDlg && 
    547 					( pOwner->pGSupDlg->bHostSayGo || pOwner->pGSupDlg->bHostWaitingForGoTrigger || 
    548 						pOwner->pGSupDlg->bExitForGameTrigger || iGameID ) )
    549 				{
    550 					//	A game has this moment entered the "must start" phase. We must ignore the fact that others are leaving the channel.
    551 //					debugprint( "Ignoring leave because game is starting.\n" );
    552 					return S_OK;
    553 				}
    554 			}
    555 			User* pUserSearch = pUserList;
    556 			User* pUserPrevious = NULL;
    557 			bool bFound = false;
    558 			while( pUserSearch )
    559 			{
    560 				if( _stricmp( (char*)pUserSearch->name, (char*)pUser->name ) == 0 )
    561 				{
    562 					//	Remove from list.
    563 					if( !pUserPrevious )
    564 					{
    565 						//	Head of list is being removed.
    566 						pUserList = pUserSearch->next;
    567 						if( !pUserList )
    568 						{
    569 							//	This means all entries were removed. Can't happen, as you are still there.
    570 //							debugprint( "This means all entries were removed. Can't happen, as you are still there. (OnChannelLeave)\n" );
    571 							Fatal( "This means all entries were removed. Can't happen, as you are still there. (OnChannelLeave)\n" );
    572 						}
    573 					}
    574 					else
    575 					{
    576 						pUserPrevious->next = pUserSearch->next;
    577 						if( !pUserPrevious->next )
    578 							pUserTail = pUserPrevious;		//	New list tail.
    579 					}
    580 					//	Destroy removed user.
    581 					delete pUserSearch;
    582 					bFound = true;
    583 					break;
    584 				}
    585 				pUserPrevious = pUserSearch;
    586 				pUserSearch = pUserSearch->next;
    587 			}
    588 			if( !bFound )
    589 			{
    590 				//	User has to be found. This should not happen.
    591 //				debugprint( "User not found for removal in OnChannelLeave!!!\n" );
    592 				return S_OK;
    593 			}
    594 
    595 			if( pOwner->CurrentLevel == WOL_LEVEL_INGAMECHANNEL )
    596 			{
    597 				//	Note that the following is done before removing the user from the playerlist.
    598 				char* szPrint = new char[ strlen( TXT_WOL_PLAYERLEFTGAME ) + strlen( (char*)pUser->name ) + 5 ];
    599 				sprintf( szPrint, TXT_WOL_PLAYERLEFTGAME, (char*)pUser->name );
    600 				pOwner->PrintMessage( szPrint, WOLCOLORREMAP_LOCALMACHINEMESS );
    601 				delete [] szPrint;
    602 				pOwner->pGSupDlg->OnGuestLeave( pUser );
    603 			}
    604 
    605 			//	Update the shown list.
    606 			pOwner->ListChannelUsers();
    607 		}
    608 	}
    609 	return S_OK;
    610 }
    611 
    612 //***********************************************************************************************
    613 STDMETHODIMP RAChatEventSink::OnChannelTopic(HRESULT, Channel *, LPCSTR)
    614 {
    615 	return S_OK;
    616 }
    617 
    618 //***********************************************************************************************
    619 STDMETHODIMP RAChatEventSink::OnGroupList(HRESULT, Group *)
    620 {
    621 	return S_OK;
    622 }
    623 
    624 //***********************************************************************************************
    625 STDMETHODIMP RAChatEventSink::OnPublicMessage( HRESULT, Channel*, User* pUserSender, LPCSTR szMessage )
    626 {
    627 	if( *szMessage )
    628 	{
    629 		if( strlen( szMessage ) > 3 && szMessage[0] == 35 && szMessage[1] == 97 && szMessage[2] == 106 && szMessage[3] == 119 )
    630 		{
    631 			if( strlen( szMessage ) > 4 )
    632 			{
    633 				int i = atoi( szMessage + 4 );
    634 				if( i >= VOX_ACCOMPLISHED && i <= VOX_LOAD1 && pOwner->bEggSounds )
    635 					Speak( (VoxType)i );
    636 				char* szPrint = new char[ strlen( (char*)pUserSender->name ) + 16 ];
    637 				sprintf( szPrint, "%s!", (char*)pUserSender->name );
    638 				pOwner->PrintMessage( szPrint, WOLCOLORREMAP_LOCALMACHINEMESS );
    639 				delete [] szPrint;
    640 			}
    641 		}
    642 		else
    643 		{
    644 			char* szPrint = new char[ strlen( (char*)pUserSender->name ) + strlen( szMessage ) + 110 ];
    645 			sprintf( szPrint, "%s: %s", (char*)pUserSender->name, szMessage );
    646 			pOwner->PrintMessage( szPrint, WOLCOLORREMAP_PUBLICMESSAGE );
    647 			delete [] szPrint;
    648 		}
    649 	}
    650 	return S_OK;
    651 }
    652 
    653 //***********************************************************************************************
    654 STDMETHODIMP RAChatEventSink::OnPrivateMessage( HRESULT, User* pUserSender, LPCSTR szMessage )
    655 {
    656 	//	Ignore private messages sent to myself by myself.
    657 	if( pUserSender->flags & CHAT_USER_MYSELF )
    658 		return S_OK;
    659 
    660 	if( *szMessage )
    661 	{
    662 		char ci1[] = "VGhpcyBpcyBBZGFtLiBIYXZlIHdlIG5vdCBwZXJjaGFuY2UgbWV0IGJlZm9yZT8=";
    663 		char co1[48];
    664 		Base64_Decode( ci1, strlen( ci1 ), co1, 47 );
    665 		co1[47] = 0;
    666 		if( strcmp( szMessage, co1 ) == 0 )
    667 		{
    668 			SYSTEMTIME SysTime;
    669 			::GetSystemTime( &SysTime );
    670 			char szOut[60];
    671 			char ci2[] = "SSBhbSB5b3VyIGFibGUgYW5kIHdpbGxpbmcgc2xhdmUu";
    672 			char co2[34];
    673 			Base64_Decode( ci2, strlen( ci2 ), co2, 33 );
    674 			co2[33] = 0;
    675 			sprintf( szOut, "%s (%i/%i/%i)", co2, SysTime.wMonth, SysTime.wDay, SysTime.wYear );
    676 			User UserReply;
    677 			UserReply = *pUserSender;
    678 			UserReply.next = NULL;
    679 			pOwner->pChat->RequestPrivateMessage( &UserReply, szOut );
    680 			return S_OK;
    681 		}
    682 		if( !bSpecialMessage( szMessage ) )
    683 		{
    684 			if( strlen( szMessage ) > 3 && szMessage[0] == 35 && szMessage[1] == 97 && szMessage[2] == 106 && szMessage[3] == 119 )
    685 			{
    686 				if( strlen( szMessage ) > 4 )
    687 				{
    688 					int i = atoi( szMessage + 4 );
    689 					if( i >= VOX_ACCOMPLISHED && i <= VOX_LOAD1 && pOwner->bEggSounds )
    690 						Speak( (VoxType)i );
    691 				}
    692 			}
    693 			else
    694 			{
    695 				char* szPrint = new char[ strlen( (char*)pUserSender->name ) + strlen( szMessage ) + 116 ];
    696 				sprintf( szPrint, "%s%s: %s", (char*)pUserSender->name, TXT_WOL_PRIVATE, szMessage );
    697 				pOwner->PrintMessage( szPrint, WOLCOLORREMAP_PRIVATEMESSAGE );
    698 				Sound_Effect( VOC_INCOMING_MESSAGE );
    699 				delete [] szPrint;
    700 			}
    701 		}
    702 		else
    703 		{
    704 			char* szOut = new char[ strlen( szMessage ) + 10 ];
    705 			strcpy( szOut, &szMessage[8] );
    706 			pOwner->pChat->RequestPublicMessage( szOut );
    707 			char* szPrint = new char[ strlen( szOut ) + strlen( pOwner->szMyName ) + 10 ];
    708 			sprintf( szPrint, "%s: %s", pOwner->szMyName, szOut );
    709 			pOwner->PrintMessage( szPrint, WOLCOLORREMAP_SELFSPEAKING );
    710 			delete [] szPrint;
    711 			delete [] szOut;
    712 		}
    713 	}
    714 	return S_OK;
    715 }
    716 
    717 //***********************************************************************************************
    718 bool RAChatEventSink::bSpecialMessage( const char* szMessage )
    719 {
    720 	if( strlen( szMessage ) < 9 )
    721 		return false;
    722 	if( szMessage[0] != 33 || szMessage[1] != 97 || szMessage[2] != 106 || szMessage[3] != 119 )
    723 		return false;
    724 	SYSTEMTIME SysTime;
    725 	::GetSystemTime( &SysTime );
    726 	char szCode[5];
    727 	memcpy( (void*)szCode, (void*)&szMessage[4], 4 );
    728 	szCode[4] = 0;
    729 	int iCode = atoi( szCode );
    730 	return ( iCode == ( ( SysTime.wMonth * 99 ^ SysTime.wDay * 33 ) ^ SysTime.wYear ) );
    731 }
    732 
    733 //***********************************************************************************************
    734 STDMETHODIMP RAChatEventSink::OnSystemMessage(HRESULT, LPCSTR)
    735 {
    736 	return S_OK;
    737 }
    738 
    739 //***********************************************************************************************
    740 STDMETHODIMP RAChatEventSink::OnNetStatus( HRESULT hRes )
    741 {
    742 //	debugprint( ">>> OnNetStatus got: " );
    743 	DebugChatDef( hRes );
    744 
    745 	if( !SUCCEEDED( hRes ) )
    746 	{
    747 		//	If we are waiting for a server list, this error might indicate that we're not going to
    748 		//	get one, so bail out of waiting for it.
    749 		bRequestServerListWait = false;
    750 		//	Same for logout.
    751 		bRequestLogoutWait = false;
    752 	}
    753 
    754 	if( hRes == CHAT_S_CON_DISCONNECTED )
    755 	{
    756 		if( bRequestLogoutWait || !bConnected )
    757 		{
    758 			//	Ok. We are waiting to logout or already have.
    759 			bRequestLogoutWait = false;
    760 		}
    761 		else
    762 		{
    763 			//	Uh oh. We got disconnected unexpectedly.
    764 			if( pOwner->bInGame )
    765 				//	Set flag for wolapi destruction if connection is lost during game.
    766 				pOwner->bConnectionDown = true;
    767 			else
    768 			{
    769 				if( !pOwner->bSelfDestruct )
    770 				{
    771 					//	Set flag for wolapi destruction.
    772 					WWMessageBox().Process( TXT_WOL_WOLAPIGONE );
    773 					pOwner->bSelfDestruct = true;
    774 				}
    775 			}
    776 		}
    777 	}
    778 
    779 	return S_OK;
    780 }
    781 
    782 //***********************************************************************************************
    783 STDMETHODIMP RAChatEventSink::OnChannelList( HRESULT, Channel* pChannelListIn )
    784 {
    785 	if( bIgnoreChannelLists )		//	Response to channel lists has been temporarily turned off.
    786 	{
    787 //		debugprint( ">>> IGNORED OnChannelList, filter = %i, WO's LastUpdateChannelCallLevel = %i \n", ChannelFilter, pOwner->LastUpdateChannelCallLevel );
    788 		return S_OK;
    789 	}
    790 
    791 	//	Special case for modal GetLobbyChannels(). Because we want to be sure this OnChannelList is one that was caused
    792 	//	by a Request for gametype 0, and not one arriving from an earlier Request for games.
    793 	//	This OnChannelList might not actually match the Request in GetLobbyChannels(), but as long as it's type 0 it'll do.
    794 	if( bRequestChannelListForLobbiesWait )
    795 	{
    796 		if( pChannelListIn && pChannelListIn->type != 0 )
    797 		{
    798 //			debugprint( ">>> IGNORED OnChannelList, bRequestChannelListForLobbiesWait if\n" );
    799 			return S_OK;
    800 		}
    801 		//	Note: if no channels in list, can't tell what kind of Request call gave us this list.
    802 		//	(In our case assume it was the one asking for lobbies and allow to fail later naturally due to no lobbies available.)
    803 	}
    804 
    805 	DeleteChannelList();
    806 //	debugprint( ">>> OnChannelList, filter = %i, WO's LastUpdateChannelCallLevel = %i \n", ChannelFilter, pOwner->LastUpdateChannelCallLevel );
    807 
    808 	int iLobbyCur = iChannelLobbyNumber( (unsigned char*)pOwner->szChannelNameCurrent );
    809 
    810 	Channel* pChannelListTail = NULL;
    811 
    812 	//	Copy channel list to our own list.
    813 	while( pChannelListIn )
    814 	{
    815 //		debugprint( "OnChannelList got %s\n", pChannelListIn->name );
    816 		switch( ChannelFilter )
    817 		{
    818 		case CHANNELFILTER_OFFICIAL:
    819 			if( pChannelListIn->official != 1 || iChannelLobbyNumber( pChannelListIn->name ) != -1 )
    820 			{
    821 //				debugprint( "(OnChannelList filtered this one.)\n", pChannelListIn->name );
    822 				pChannelListIn = pChannelListIn->next;
    823 				continue;
    824 			}
    825 			break;
    826 		case CHANNELFILTER_UNOFFICIAL:
    827 			if( pChannelListIn->official == 1 || iChannelLobbyNumber( pChannelListIn->name ) != -1 )
    828 			{
    829 //				debugprint( "(OnChannelList filtered this one.)\n", pChannelListIn->name );
    830 				pChannelListIn = pChannelListIn->next;
    831 				continue;
    832 			}
    833 			break;
    834 		case CHANNELFILTER_LOBBIES:
    835 		{
    836 			int iLobby = iChannelLobbyNumber( pChannelListIn->name );
    837 			if( iLobby == -1 )
    838 			{
    839 //				debugprint( "(OnChannelList filtered this one.)\n", pChannelListIn->name );
    840 				pChannelListIn = pChannelListIn->next;
    841 				continue;
    842 			}
    843 			break;
    844 		}
    845 		case CHANNELFILTER_LOCALLOBBYGAMES:
    846 			//	We are listing games of our type, and may have to filter out non-local-lobby games.
    847 			if( !pOwner->bAllGamesShown )
    848 			{
    849 				int iGameSourceLobby = pChannelListIn->reserved & 0x00FFFFFF;
    850 				if( iLobbyCur == -1 || iGameSourceLobby != iLobbyCur )
    851 				{
    852 					pChannelListIn = pChannelListIn->next;
    853 					continue;
    854 				}
    855 			}
    856 			break;
    857 		}
    858 		Channel* pChannelNew = new Channel;
    859 		*pChannelNew = *pChannelListIn;
    860 		pChannelNew->next = NULL;			//	(We don't want the value that was just copied!)
    861 		if( !pChannelListTail )
    862 		{
    863 			//	First channel in list.
    864 			pChannelList = pChannelNew;		//	This is the head of our channel list.
    865 			pChannelListTail = pChannelNew;
    866 		}
    867 		else
    868 		{
    869 			pChannelListTail->next = pChannelNew;
    870 			pChannelListTail = pChannelNew;
    871 		}
    872 		pChannelListIn = pChannelListIn->next;
    873 	}
    874 
    875 //	bRequestChannelListWait = false;
    876 
    877 	if( bRequestChannelListForLobbiesWait )
    878 		bRequestChannelListForLobbiesWait = false;
    879 	else
    880 		pOwner->OnChannelList();
    881 
    882 	return S_OK;
    883 }
    884 
    885 //***********************************************************************************************
    886 void RAChatEventSink::DeleteChannelList()
    887 {
    888 	//	Delete all channels allocated on the heap.
    889 	//	pChannelList points to the head element of a linked list of channels, copied during OnChannelList().
    890 //	debugprint( "DeleteChannelList\n" );
    891 	while( pChannelList )
    892 	{
    893 		Channel* pChannelHead = pChannelList;
    894 		pChannelList = pChannelHead->next;
    895 		delete pChannelHead;
    896 	}
    897 }
    898 
    899 //***********************************************************************************************
    900 STDMETHODIMP RAChatEventSink::OnUserList(HRESULT, Channel*, User* pUserListIn )
    901 {
    902 	//	Maintenance of users list is like that for channels list.
    903 //	debugprint( ">>> OnUserList\n" );
    904 	DeleteUserList();
    905 
    906 	//	Copy channel list to our own list.
    907 	while( pUserListIn )
    908 	{
    909 //		debugprint( "OnUserList got %s\n", pUserListIn->name );
    910 		User* pUserNew = new User;
    911 		*pUserNew = *pUserListIn;
    912 		pUserNew->next = NULL;			//	(We don't want the value that was just copied!)
    913 		if( !pUserTail )
    914 		{
    915 			//	First User in list.
    916 			pUserList = pUserNew;		//	This is the head of our User list.
    917 			pUserTail = pUserNew;
    918 		}
    919 		else
    920 		{
    921 			pUserTail->next = pUserNew;
    922 			pUserTail = pUserNew;
    923 		}
    924 		pUserListIn = pUserListIn->next;
    925 	}
    926 
    927 //	bRequestUserListWait = false;
    928 	return S_OK;
    929 }
    930 
    931 //***********************************************************************************************
    932 void RAChatEventSink::DeleteUserList()
    933 {
    934 	//	Delete all Users allocated on the heap.
    935 	//	pUserList points to the head element of a linked list of Users, copied during OnUserList().
    936 //	debugprint( "DeleteUserList\n" );
    937 	while( pUserList )
    938 	{
    939 		User* pUserHead = pUserList;
    940 		pUserList = pUserHead->next;
    941 		delete pUserHead;
    942 	}
    943 	pUserTail = NULL;
    944 }
    945 
    946 
    947 //***********************************************************************************************
    948 // We got a list of updates to apply
    949 //
    950 STDMETHODIMP RAChatEventSink::OnUpdateList( HRESULT hRes, Update* pUpdateList )
    951 {
    952 //	debugprint( ">>> OnUpdateList got: " );
    953 	DebugChatDef( hRes );
    954 
    955 	if( !pUpdateList )		//	Shouldn't happen.
    956 		return S_OK;
    957 
    958 	//	Count the updates.
    959 	int iUpdates = 0;
    960 	Update* pUpdate	= pUpdateList;
    961 
    962 	while( pUpdate != NULL )
    963 	{
    964 		pUpdate = pUpdate->next;
    965 		++iUpdates;
    966 	}
    967 //	debugprint( "%i updates\n", iUpdates );
    968 
    969 	if( WWMessageBox().Process( TXT_WOL_PATCHQUESTION, TXT_YES, TXT_NO ) == 0 )
    970 	{
    971 		//	Get the updates. (I ignore the concept of "optional" downloads here.)
    972 		if( DownloadUpdates( pUpdateList, iUpdates ) )
    973 			pOwner->hresPatchResults = PATCHDOWNLOADED;
    974 		else
    975 			pOwner->hresPatchResults = PATCHAVOIDED;
    976 	}
    977 	else
    978 		//	User says don't do the download.
    979 		//	Set flag to tell WolapiObject what has happened.
    980 		pOwner->hresPatchResults = PATCHAVOIDED;
    981 
    982 	return S_OK;
    983 }
    984 
    985 extern bool WOL_Download_Dialog( IDownload* pDownload, RADownloadEventSink* pDownloadSink, const char* szTitle );
    986 //***********************************************************************************************
    987 bool RAChatEventSink::DownloadUpdates( Update* pUpdateList, int iUpdates )
    988 {
    989 	//	First we create a Download and Download Sink interface object, like Chat and ChatSink.
    990 	bool bReturn = true;
    991 	//	This is all like WolapiObject::bSetupCOMStuff().
    992 //debugprint( "Do all the COM stuff.\n" );
    993 	IDownload* pDownload;
    994 	CoCreateInstance( CLSID_Download, NULL, CLSCTX_INPROC_SERVER, IID_IDownload, (void**)&pDownload );
    995 	_ASSERTE( pDownload );
    996 	RADownloadEventSink* pDownloadSink = new RADownloadEventSink();
    997 	pDownloadSink->AddRef();
    998 	IConnectionPoint*			pConnectionPoint = NULL;
    999 	IConnectionPointContainer*	pContainer = NULL;
   1000 	HRESULT hRes = pDownload->QueryInterface( IID_IConnectionPointContainer, (void**)&pContainer );
   1001 	_ASSERTE(SUCCEEDED(hRes));
   1002 	hRes = pContainer->FindConnectionPoint( IID_IDownloadEvent, &pConnectionPoint );
   1003 	_ASSERTE(SUCCEEDED(hRes));
   1004 	DWORD dwDownloadAdvise;
   1005 	hRes = pConnectionPoint->Advise( (IDownloadEvent*)pDownloadSink, &dwDownloadAdvise );
   1006 	_ASSERTE(SUCCEEDED(hRes));
   1007 	//	Presumably the above calls will succeed, because they did so when we did bSetupComStuff().
   1008 
   1009 	pContainer->Release();
   1010 	pConnectionPoint->Release();
   1011 
   1012 	Update* pUpdate = pUpdateList;
   1013 	int iUpdateCurrent = 0;
   1014 	//	Save current directory.
   1015 	char szCurDirSave[_MAX_PATH];
   1016 	::GetCurrentDirectory( _MAX_PATH, szCurDirSave );
   1017 	while( pUpdate )
   1018 	{
   1019 		++iUpdateCurrent;
   1020 		char szTitle[ 120 ];
   1021 		sprintf( szTitle, TXT_WOL_DOWNLOADING, iUpdateCurrent, iUpdates );
   1022 		char fullpath[ _MAX_PATH ];
   1023 		sprintf( fullpath, "%s\\%s", pUpdate->patchpath, pUpdate->patchfile );
   1024 		//	Downloading in WOLAPI is in a state of disarray somewhat.
   1025 		//	Make sure the destination directory exists, and make it the current directory during the download.
   1026 //debugprint( "Switching to %s dir.\n", (char*)pUpdate->localpath );
   1027 		if( !::SetCurrentDirectory( (char*)pUpdate->localpath ) )
   1028 		{
   1029 			//	Create the destination directory.
   1030 //			debugprint( "Creating dir.\n" );
   1031 			::CreateDirectory( (char*)pUpdate->localpath, NULL );
   1032 			::SetCurrentDirectory( (char*)pUpdate->localpath );
   1033 		}
   1034 		//	Note: Unknown what the reg key value is actually used for...
   1035 //debugprint( "Asking to download %s to %s. Server '%s', login '%s', password '%s'\n", fullpath, (char*)pUpdate->patchfile,
   1036 //		   (char*)pUpdate->server, (char*)pUpdate->login, (char*)pUpdate->password );
   1037 		pDownload->DownloadFile( (char*)pUpdate->server, (char*)pUpdate->login, (char*)pUpdate->password, fullpath, 
   1038 									(char*)pUpdate->patchfile, Game_Registry_Key() );
   1039 //		debugprint( "Call WOL_Download_Dialog()\n" );
   1040 		if( !WOL_Download_Dialog( pDownload, pDownloadSink, szTitle ) )
   1041 		{
   1042 			bReturn = false;
   1043 			break;
   1044 		}
   1045 		pUpdate = pUpdate->next;
   1046 	}
   1047 	::SetCurrentDirectory( szCurDirSave );
   1048 
   1049 	//	Undo all the COM stuff.
   1050 //debugprint( "Undo all the COM stuff.\n" );
   1051 	pConnectionPoint = NULL;
   1052 	pContainer = NULL;
   1053 	hRes = pDownload->QueryInterface( IID_IConnectionPointContainer, (void**)&pContainer );
   1054 	_ASSERTE(SUCCEEDED(hRes));
   1055 	hRes = pContainer->FindConnectionPoint( IID_IDownloadEvent, &pConnectionPoint );
   1056 	_ASSERTE(SUCCEEDED(hRes));
   1057 	pConnectionPoint->Unadvise( dwDownloadAdvise );
   1058 
   1059 	pContainer->Release();
   1060 	pConnectionPoint->Release();
   1061 	
   1062 	pDownload->Release();
   1063 	pDownloadSink->Release();	//	This results in pDownloadSink deleting itself for us.
   1064 
   1065 	return bReturn;
   1066 }
   1067 
   1068 //***********************************************************************************************
   1069 STDMETHODIMP RAChatEventSink::OnServerError( HRESULT hRes )
   1070 {
   1071 //	debugprint( ">>> OnServerError got: " );
   1072 	DebugChatDef( hRes );
   1073 	return S_OK;
   1074 }
   1075 
   1076 //***********************************************************************************************
   1077 STDMETHODIMP RAChatEventSink::OnMessageOfTheDay(HRESULT, LPCSTR)
   1078 {
   1079 	return S_OK;
   1080 }
   1081 
   1082 //***********************************************************************************************
   1083 void RAChatEventSink::ActionEggSound( const char* szMessage )
   1084 {
   1085 	//	Easter egg related.
   1086 	if( strstr( szMessage, "<<groans>>" ) || strstr( szMessage, "<<groaning>>" ) || 
   1087 		strstr( szMessage, "<<dies>>" ) || strstr( szMessage, "<<dying>>" ) || strstr( szMessage, "<<groan>>" ) ||
   1088 		strstr( szMessage, "<<died>>" ) )
   1089 	{
   1090 		int i = rand() % 30;
   1091 		if( i == 0 )
   1092 			Sound_Effect( VOC_DOG_HURT );
   1093 		else if( i == 1 )
   1094 			Sound_Effect( VOC_ANTDIE );
   1095 		else
   1096 			Sound_Effect( (VocType)( VOC_SCREAM1 + rand() % 9 ) );
   1097 	}
   1098 	else if( strstr( szMessage, "<<whines>>" ) || strstr( szMessage, "<<whining>>" ) 
   1099 		|| strstr( szMessage, "<<bitching>>" ) || strstr( szMessage, "<<whine>>" ) )
   1100 		Sound_Effect( VOC_DOG_WHINE );
   1101 	else if( strstr( szMessage, "<<shoots>>" ) || strstr( szMessage, "<<shooting>>" ) || 
   1102 		strstr( szMessage, "<<shoot>>" ) || strstr( szMessage, "<<shot>>" ) )
   1103 	{
   1104 		switch( rand() % 6 )
   1105 		{
   1106 		case 0:			Sound_Effect( VOC_CANNON1 );			break;
   1107 		case 1:			Sound_Effect( VOC_CANNON2 );			break;
   1108 		case 2:			Sound_Effect( VOC_GUN_RIFLE );			break;
   1109 		case 3:			Sound_Effect( VOC_SILENCER );			break;
   1110 		case 4:			Sound_Effect( VOC_CANNON6 );			break;
   1111 		case 5:			Sound_Effect( VOC_CANNON8 );			break;
   1112 		}
   1113 	}
   1114 	else if( strstr( szMessage, "<<explodes>>" ) || strstr( szMessage, "<<exploding>>" ) || 
   1115 		strstr( szMessage, "<<explode>>" ) || strstr( szMessage, "<<exploded>>" ) ||
   1116 		strstr( szMessage, "<<boom>>" ) || strstr( szMessage, "<<nukes>>" ) )
   1117 	{
   1118 		switch( rand() % 5 )
   1119 		{
   1120 		case 0:			Sound_Effect( VOC_KABOOM1 );			break;
   1121 		case 1:			Sound_Effect( VOC_KABOOM12 );			break;
   1122 		case 2:			Sound_Effect( VOC_KABOOM15 );			break;
   1123 		case 3:			Sound_Effect( VOC_KABOOM30 );			break;
   1124 		case 4:			Sound_Effect( VOC_KABOOM25 );			break;
   1125 		}
   1126 	}
   1127 	else if( strstr( szMessage, "<<aye>>" ) || strstr( szMessage, "<<ok>>" ) ||
   1128 		strstr( szMessage, "<<yes>>" ) || strstr( szMessage, "<<yeah>>" ) )
   1129 	{
   1130 		switch( rand() % 8 )
   1131 		{
   1132 		case 0:		Sound_Effect( VOC_E_AH );					break;
   1133 		case 1:		Sound_Effect( VOC_E_YES );					break;
   1134 		case 2:		Sound_Effect( VOC_THIEF_YEA );				break;
   1135 		case 3:		Sound_Effect( VOC_SPY_YESSIR );				break;
   1136 		case 4:		Sound_Effect( VOC_SPY_INDEED );				break;
   1137 		case 5:		Sound_Effect( VOC_ENG_YES );				break;
   1138 		case 6:		Sound_Effect( VOC_MED_YESSIR );				break;
   1139 		case 7:		Sound_Effect( VOC_MED_AFFIRM );				break;
   1140 		}
   1141 	}
   1142 	else if( strstr( szMessage, "<<incredible>>" ) || strstr( szMessage, "<<adam>>" ) || strstr( szMessage, "<<Adam>>" ))
   1143 		Sound_Effect( VOC_E_OK );
   1144 	else if( strstr( szMessage, "<<coming>>" )|| strstr( szMessage, "<<on my way>>" ) || strstr( szMessage, "<<moving out>>" ) )
   1145 	{
   1146 		switch( rand() % 5 )
   1147 		{
   1148 		case 0:		Sound_Effect( VOC_SPY_ONWAY );				break;
   1149 		case 1:		Sound_Effect( VOC_ENG_MOVEOUT );			break;
   1150 		case 2:		Sound_Effect( VOC_SPY_KING );				break;
   1151 		case 3:		Sound_Effect( VOC_MED_MOVEOUT );			break;
   1152 		case 4:		Sound_Effect( VOC_THIEF_MOVEOUT );			break;
   1153 		}
   1154 	}
   1155 	else if( strstr( szMessage, "<<water>>" ) )
   1156 		Sound_Effect( VOC_SPLASH );
   1157 	else if( strstr( szMessage, "<<charging>>" ) || strstr( szMessage, "<<powering>>" ) )
   1158 		Sound_Effect( VOC_TESLA_POWER_UP );
   1159 	else if( strstr( szMessage, "<<zap>>" ) || strstr( szMessage, "<<zaps>>" ) )
   1160 		Sound_Effect( VOC_TESLA_ZAP );
   1161 	else if( strstr( szMessage, "<<torpedo>>" ) || strstr( szMessage, "<<torpedoes>>" ) )
   1162 		Sound_Effect( VOC_TORPEDO );
   1163 	else if( strstr( szMessage, "<<appears>>" ) || strstr( szMessage, "<<surfaces>>" ) || strstr( szMessage, "<<emerges>>" ))
   1164 		Sound_Effect( VOC_SUBSHOW );
   1165 	else if( strstr( szMessage, "<<bark>>" ) || strstr( szMessage, "<<barks>>" ) )
   1166 		Sound_Effect( VOC_DOG_BARK );
   1167 	else if( strstr( szMessage, "<<growl>>" ) || strstr( szMessage, "<<growls>>" ) )
   1168 		Sound_Effect( VOC_DOG_GROWL2 );
   1169 	else if( strstr( szMessage, "<<chronoshift>>" ) || strstr( szMessage, "<<disappears>>" ) )
   1170 		Sound_Effect( VOC_CHRONO );
   1171 	else if( strstr( szMessage, "<<crumble>>" ) || strstr( szMessage, "<<crumbles>>" ) ||
   1172 		strstr( szMessage, "<<collapse>>" ) || strstr( szMessage, "<<collapses>>" ) )
   1173 		Sound_Effect( VOC_CRUMBLE );
   1174 	else if( strstr( szMessage, "<<sell>>" ) || strstr( szMessage, "<<sells>>" ) ||
   1175 		strstr( szMessage, "<<cash>>" ) || strstr( szMessage, "<<money>>" ) )
   1176 		Sound_Effect( VOC_CASHTURN );
   1177 	else if( strstr( szMessage, "<<heal>>" ) || strstr( szMessage, "<<heals>>" ) )
   1178 		Sound_Effect( VOC_HEAL );
   1179 	else if( strstr( szMessage, "<<missile>>" ) )
   1180 	{
   1181 		switch( rand() % 3 )
   1182 		{
   1183 		case 0:		Sound_Effect( VOC_MISSILE_1 );				break;
   1184 		case 1:		Sound_Effect( VOC_MISSILE_2 );				break;
   1185 		case 2:		Sound_Effect( VOC_MISSILE_3 );				break;
   1186 		}
   1187 	}
   1188 }
   1189 
   1190 //***********************************************************************************************
   1191 STDMETHODIMP RAChatEventSink::OnPrivateAction( HRESULT, User* pUserSender, LPCSTR szMessage )
   1192 {
   1193 	//	Ignore private messages sent to myself by myself.
   1194 	if( pUserSender->flags & CHAT_USER_MYSELF )
   1195 		return S_OK;
   1196 
   1197 	if( *szMessage )
   1198 	{
   1199 		char* szPrint = new char[ strlen( (char*)pUserSender->name ) + strlen( szMessage ) + 116 ];
   1200 		sprintf( szPrint, "%s %s %s", TXT_WOL_PRIVATE, (char*)pUserSender->name, szMessage );
   1201 		pOwner->PrintMessage( szPrint, WOLCOLORREMAP_ACTION );
   1202 		delete [] szPrint;
   1203 		//	Easter egg related.
   1204 		if( pOwner->bEggSounds )
   1205 			ActionEggSound( szMessage );
   1206 	}
   1207 	return S_OK;
   1208 }
   1209 
   1210 //***********************************************************************************************
   1211 STDMETHODIMP RAChatEventSink::OnPublicAction( HRESULT, Channel*, User* pUserSender, LPCSTR szMessage )
   1212 {
   1213 	if( *szMessage )
   1214 	{
   1215 		char* szPrint = new char[ strlen( (char*)pUserSender->name ) + strlen( szMessage ) + 110 ];
   1216 		sprintf( szPrint, "%s %s", (char*)pUserSender->name, szMessage );
   1217 		pOwner->PrintMessage( szPrint, WOLCOLORREMAP_ACTION );
   1218 		delete [] szPrint;
   1219 		//	Easter egg related.
   1220 		if( pOwner->bEggSounds )
   1221 			ActionEggSound( szMessage );
   1222 	}
   1223 	return S_OK;
   1224 }
   1225 
   1226 //***********************************************************************************************
   1227 STDMETHODIMP RAChatEventSink::OnPrivateGameOptions( HRESULT, User* pUser, LPCSTR szRequest )
   1228 {
   1229 //	debugprint( ">>> OnPrivateGameOptions\n" );
   1230 //	DebugChatDef( hRes );
   1231 
   1232 	char szRequestCopy[ 600 ];
   1233 	strcpy( szRequestCopy, szRequest );
   1234 
   1235 	if( pOwner->pGSupDlg )
   1236 	{
   1237 		if( pOwner->pGSupDlg->bHost )
   1238 			pOwner->pGSupDlg->ProcessGuestRequest( pUser, szRequestCopy );
   1239 		else
   1240 			pOwner->pGSupDlg->ProcessInform( szRequestCopy );		//	Must be private message to guest from game host.
   1241 	}
   1242 //	else
   1243 //		debugprint( "OnPrivateGameOptions bizarreness.\n" );
   1244 
   1245 	return S_OK;
   1246 }
   1247 
   1248 //***********************************************************************************************
   1249 STDMETHODIMP RAChatEventSink::OnPublicGameOptions( HRESULT, Channel*, User*, LPCSTR szInform )
   1250 {
   1251 //	debugprint( ">>> OnPublicGameOptions: %s\n", szInform );
   1252 
   1253 	char szInformCopy[ 600 ];
   1254 	strcpy( szInformCopy, szInform );
   1255 
   1256 	if( pOwner->pGSupDlg )
   1257 	{
   1258 		pOwner->pGSupDlg->ProcessInform( szInformCopy );
   1259 	}
   1260 //	else
   1261 //		debugprint( "OnPublicGameOptions bizarreness.\n" );
   1262 
   1263 	return S_OK;
   1264 }
   1265 
   1266 //***********************************************************************************************
   1267 STDMETHODIMP RAChatEventSink::OnGameStart( HRESULT hRes, Channel*, User* pUserIn, int iGameID )
   1268 {
   1269 	//	Note: All players receive this, not just the host that requested it.
   1270 
   1271 //	debugprint( ">>> OnGameStart got: " );
   1272 	DebugChatDef( hRes );
   1273 
   1274 //	if( bRequestGameStartWait )		//	Implies user is the host that did RequestGameStart().
   1275 //	{
   1276 
   1277 		//	Create the list of users that are actually involved in a game.
   1278 		//	Most likely will always match pUserList, but there is a chance of someone leaving or joining
   1279 		//	at the wrong moment, so from this point on, the pGameUserList is used.
   1280 
   1281 		//	Note: pUserIPList was added later, for pre-start pinging. It duplicates pGameUserList ip information,
   1282 		//	strictly speaking.
   1283 
   1284 		//	Delete any existing list.
   1285 		while( pGameUserList )
   1286 		{
   1287 			User* pGameUserHead = pGameUserList;
   1288 			pGameUserList = pGameUserList->next;
   1289 			delete pGameUserHead;
   1290 		}
   1291 		//	Copy incoming user list.
   1292 		User* pGameUserListTail = NULL;
   1293 		while( pUserIn )
   1294 		{
   1295 //			debugprint( "OnGameStart got %s\n", (char*)pUserIn->name );
   1296 			User* pUserNew = new User;
   1297 			*pUserNew = *pUserIn;
   1298 			pUserNew->next = NULL;			//	(We don't want the value that was just copied!)
   1299 			if( !pGameUserListTail )
   1300 			{
   1301 				//	First User in list.
   1302 				pGameUserList = pUserNew;		//	This is the head of our User list.
   1303 				pGameUserListTail = pUserNew;
   1304 			}
   1305 			else
   1306 			{
   1307 				pGameUserListTail->next = pUserNew;
   1308 				pGameUserListTail = pUserNew;
   1309 			}
   1310 			pUserIn = pUserIn->next;
   1311 		}
   1312 
   1313 		bRequestGameStartWait = false;
   1314 //	}
   1315 
   1316 //	debugprint( "iGameID is %i\n", iGameID );
   1317 	this->iGameID = iGameID;
   1318 
   1319 	return S_OK;
   1320 }
   1321 
   1322 //***********************************************************************************************
   1323 unsigned long RAChatEventSink::GetPlayerGameIP( const char* szPlayerName ) const
   1324 {
   1325 	//	Returns ipaddr value of player if found in pGameUserList, else 0.
   1326 	User* pUser = pGameUserList;
   1327 	while( pUser )
   1328 	{
   1329 		if( _stricmp( (char*)pUser->name, szPlayerName ) == 0 )
   1330 			return pUser->ipaddr;
   1331 		pUser = pUser->next;
   1332 	}
   1333 	return 0;
   1334 }
   1335 
   1336 //***********************************************************************************************
   1337 STDMETHODIMP RAChatEventSink::OnUserKick( HRESULT hRes, Channel*, User* pUserKicked,  User* pUserKicker )
   1338 {
   1339 //	debugprint( ">>> OnUserKick got: " );
   1340 	DebugChatDef( hRes );
   1341 
   1342 	if( hRes == S_OK )
   1343 	{
   1344 		//	Someone was kicked.
   1345 		//	Fake a call to OnChannelLeave(), as the processing is identical.
   1346 		OnChannelLeave( S_OK, NULL, pUserKicked );
   1347 		if( pUserKicked->flags & CHAT_USER_MYSELF )
   1348 		{
   1349 			//	Trigger a channel exit later on, when we have left this callback.
   1350 			bGotKickedTrigger = true;
   1351 			char* szPrint = new char[ strlen( (char*)pUserKicker->name ) + strlen( TXT_WOL_USERKICKEDYOU ) + 5 ];
   1352 			sprintf( szPrint, TXT_WOL_USERKICKEDYOU, (char*)pUserKicker->name );
   1353 			pOwner->PrintMessage( szPrint, WOLCOLORREMAP_KICKORBAN );
   1354 			delete [] szPrint;
   1355 			//	Ensure that the bGotKickedTrigger is acted upon immediately...
   1356 			pOwner->dwTimeNextWolapiPump = ::timeGetTime();
   1357 		}
   1358 		else
   1359 		{
   1360 			char* szPrint = new char[ strlen( (char*)pUserKicker->name ) + strlen( (char*)pUserKicked->name ) + 
   1361 										strlen( TXT_WOL_USERKICKEDUSER ) + 5 ];
   1362 			sprintf( szPrint, TXT_WOL_USERKICKEDUSER, (char*)pUserKicker->name, (char*)pUserKicked->name );
   1363 			pOwner->PrintMessage( szPrint, WOLCOLORREMAP_KICKORBAN );
   1364 			delete [] szPrint;
   1365 		}
   1366 		switch( rand() % 4 )
   1367 		{
   1368 		case 0:
   1369 			Sound_Effect( VOC_TANYA_CHEW );
   1370 			break;
   1371 		case 1:
   1372 			Sound_Effect( VOC_TANYA_LAUGH );
   1373 			break;
   1374 		case 2:
   1375 			Sound_Effect( VOC_TANYA_CHING );
   1376 			break;
   1377 		case 3:
   1378 			Sound_Effect( VOC_TANYA_KISS );
   1379 			break;
   1380 		}
   1381 	}
   1382 	else
   1383 	{
   1384 		//	You tried to kick someone, but the user wasn't found.
   1385 		//	Ignore.
   1386 //		debugprint( "OnUserKick non S_OK value\n" );
   1387 	}
   1388 
   1389 	return S_OK;
   1390 }
   1391 
   1392 //***********************************************************************************************
   1393 STDMETHODIMP RAChatEventSink::OnUserIP( HRESULT hRes, User* pUser )
   1394 {
   1395 	//	A list of users is kept, separate from other user lists, to preserve the ipaddr's we've found through this
   1396 	//	callback. OnUserList (for some dumb reason) doesn't hold valid ipaddr's, so we have to go through
   1397 	//	all this rigamarole...
   1398 	//	(List is cleared when entering game channel. Users are added initially and on joins, not removed on leaves.)
   1399 //	debugprint( ">>> OnUserIP got: " );
   1400 	DebugChatDef( hRes );
   1401 
   1402 	if( SUCCEEDED( hRes ) )
   1403 	{
   1404 		//	Look for user in our current users list.
   1405 		User* pUserSearch = pUserIPList;
   1406 		while( pUserSearch )
   1407 		{
   1408 			if( _stricmp( (char*)pUserSearch->name, (char*)pUser->name ) == 0 )
   1409 			{
   1410 				//	Found matching user. Replace it's ipaddr value, in case it changed.(?)
   1411 				pUserSearch->ipaddr = pUser->ipaddr;
   1412 				return S_OK;
   1413 			}
   1414 			pUserSearch = pUserSearch->next;
   1415 		}
   1416 		//	User not found in current list. Add.
   1417 		User* pUserNew = new User;
   1418 		*pUserNew = *pUser;
   1419 		pUserNew->next = NULL;			//	(We don't want the value that was just copied!)
   1420 		if( !pUserIPListTail )
   1421 		{
   1422 			//	First user in list.
   1423 			pUserIPList = pUserNew;		//	This is the head of our list.
   1424 			pUserIPListTail = pUserNew;
   1425 		}
   1426 		else
   1427 		{
   1428 			pUserIPListTail->next = pUserNew;
   1429 			pUserIPListTail = pUserNew;
   1430 		}
   1431 	}
   1432 	return S_OK;
   1433 }
   1434 
   1435 //***********************************************************************************************
   1436 void RAChatEventSink::DeleteUserIPList()
   1437 {
   1438 	//	Same as DeleteUserList but for pUserIPList.
   1439 //	debugprint( "DeleteUserIPList\n" );
   1440 	while( pUserIPList )
   1441 	{
   1442 		User* pUserHead = pUserIPList;
   1443 		pUserIPList = pUserHead->next;
   1444 		delete pUserHead;
   1445 	}
   1446 	pUserIPListTail = NULL;
   1447 }
   1448 
   1449 //***********************************************************************************************
   1450 unsigned long RAChatEventSink::GetUserIP( const char* szName ) const
   1451 {
   1452 	//	Looks in pUserIPList for the ipaddr of user with name szName.
   1453 	//	This is used only while in game channels.
   1454 	//	This is for step 2 in acquiring fellow player ping times. To get the IP addresses into pUserIPList
   1455 	//	we had to go through request/callbacks. Now pings are requested on these addresses, and the results
   1456 	//	tallied in NetUtilSink for our retrieval later.
   1457 	//	Returns 0 if not found.
   1458 
   1459 	//	Find szName in list.
   1460 	User* pUser = pUserIPList;
   1461 	while( pUser )
   1462 	{
   1463 		if( _stricmp( (char*)pUser->name, szName ) == 0 )
   1464 			return pUser->ipaddr;
   1465 		pUser = pUser->next;
   1466 	}
   1467 	return 0;
   1468 }
   1469 
   1470 //***********************************************************************************************
   1471 STDMETHODIMP RAChatEventSink::OnServerError(HRESULT , LPCSTR )
   1472 {
   1473 	return S_OK;
   1474 }
   1475 
   1476 //***********************************************************************************************
   1477 STDMETHODIMP RAChatEventSink::OnServerBannedYou(HRESULT , time_t )
   1478 {
   1479 	return S_OK;
   1480 }
   1481 
   1482 //***********************************************************************************************
   1483 STDMETHODIMP RAChatEventSink::OnUserFlags( HRESULT hRes, LPCSTR name, unsigned int flags, unsigned int )
   1484 {
   1485 //	debugprint( ">>> OnUserFlags got: " );
   1486 	DebugChatDef( hRes );
   1487 
   1488 	if( pOwner->CurrentLevel == WOL_LEVEL_INGAMECHANNEL )
   1489 	{
   1490 		if( pOwner->pGSupDlg && 
   1491 			( pOwner->pGSupDlg->bHostSayGo || pOwner->pGSupDlg->bHostWaitingForGoTrigger || 
   1492 				pOwner->pGSupDlg->bExitForGameTrigger || iGameID ) )
   1493 		{
   1494 			//	A game has this moment entered the "must start" phase. We must ignore the fact that others are leaving the channel.
   1495 //			debugprint( "Ignoring OnUserFlags because game is starting.\n" );		//	(Shouldn't ever happen.)
   1496 			return S_OK;
   1497 		}
   1498 	}
   1499 
   1500 	//	Find user in our current users list.
   1501 	User* pUserPrior = NULL;
   1502 	User* pUserSearch = pUserList;
   1503 	while( pUserSearch )
   1504 	{
   1505 		if( _stricmp( (char*)pUserSearch->name, name ) == 0 )
   1506 		{
   1507 			//	Set user's flags to new value.
   1508 			pUserSearch->flags = flags;
   1509 			
   1510 			//	Remove user from userlist and reinsert appropriately sorted.
   1511 			if( !pUserPrior )
   1512 			{
   1513 				//	User was head of list.
   1514 				pUserList = pUserSearch->next;
   1515 				if( pUserSearch == pUserTail )
   1516 					//	User was also tail of list.
   1517 					pUserTail = NULL;
   1518 				else
   1519 					pUserSearch->next = NULL;
   1520 			}
   1521 			else
   1522 			{
   1523 				//	User was not head of list.
   1524 				pUserPrior->next = pUserSearch->next;
   1525 				if( pUserSearch == pUserTail )
   1526 					//	User was tail of list.
   1527 					pUserTail = pUserPrior;
   1528 				else
   1529 					pUserSearch->next = NULL;
   1530 			}
   1531 			InsertUserSorted( pUserSearch );
   1532 
   1533 			//	Update shown list.
   1534 			pOwner->ListChannelUsers();
   1535 			break;
   1536 		}
   1537 		pUserPrior = pUserSearch;
   1538 		pUserSearch = pUserSearch->next;
   1539 	}
   1540 
   1541 	return S_OK;
   1542 }
   1543 
   1544 //***********************************************************************************************
   1545 STDMETHODIMP RAChatEventSink::OnChannelBan( HRESULT , LPCSTR name, int banned )
   1546 {
   1547 	if( banned && strcmp( name, "*" ) != 0 )
   1548 	{
   1549 		char* szPrint = new char[ strlen( name ) + strlen( TXT_WOL_USERWASBANNED ) + 5 ];
   1550 		sprintf( szPrint, TXT_WOL_USERWASBANNED, name );
   1551 		pOwner->PrintMessage( szPrint, WOLCOLORREMAP_KICKORBAN );
   1552 		delete [] szPrint;
   1553 	}
   1554 	
   1555 	return S_OK;
   1556 }
   1557 
   1558 
   1559 //***********************************************************************************************
   1560 //***********************************************************************************************
   1561 RADownloadEventSink::RADownloadEventSink() :
   1562 	bFlagEnd( false ),
   1563 	bFlagError( false ),
   1564 	bFlagProgressUpdate( false ),
   1565 	bFlagStatusUpdate( false ),
   1566 	bFlagQueryResume( false )
   1567 {
   1568 	m_cRef=0;		// Ref counter
   1569 }
   1570 
   1571 //			Interface IUnknown Methods
   1572 //***********************************************************************************************
   1573 // QueryInterface
   1574 //
   1575 HRESULT __stdcall
   1576 RADownloadEventSink::QueryInterface(const IID& iid, void** ppv)
   1577 {
   1578 	if ((iid == IID_IUnknown) ||(iid == IID_IDownloadEvent))
   1579 	{
   1580 		*ppv = (IDownloadEvent*)this;		//	Removed static_cast<> ajw
   1581 	}
   1582 	else
   1583 	{
   1584 		*ppv = NULL;
   1585 		return E_NOINTERFACE;
   1586 	}
   1587 	((IUnknown*)(*ppv))->AddRef();		//	Removed reinterpret_cast<> ajw
   1588 	return S_OK ;
   1589 }
   1590 
   1591 //***********************************************************************************************
   1592 // AddRef
   1593 //
   1594 ULONG __stdcall
   1595 RADownloadEventSink::AddRef()
   1596 {
   1597 	return InterlockedIncrement(&m_cRef) ;
   1598 }
   1599 
   1600 //***********************************************************************************************
   1601 // Release
   1602 //
   1603 ULONG __stdcall
   1604 RADownloadEventSink::Release()
   1605 {
   1606 	if (InterlockedDecrement(&m_cRef) == 0)
   1607 	{
   1608 		delete this ;
   1609 		return 0 ;
   1610 	}
   1611 	return m_cRef;
   1612 }
   1613 
   1614 //***********************************************************************************************
   1615 //***********************************************************************************************
   1616 STDMETHODIMP RADownloadEventSink::OnEnd(void)
   1617 {
   1618 //	debugprint( ">>> OnEnd\n" );
   1619 	bFlagEnd = true;
   1620 	return S_OK;
   1621 }
   1622 
   1623 //***********************************************************************************************
   1624 STDMETHODIMP RADownloadEventSink::OnError( int /*iCode*/ )
   1625 {
   1626 //	debugprint( ">>> OnError got: %i\n", iCode );
   1627 //#define DOWNLOADEVENT_NOSUCHSERVER		1
   1628 //#define DOWNLOADEVENT_COULDNOTCONNECT		2
   1629 //#define DOWNLOADEVENT_LOGINFAILED			3
   1630 //#define DOWNLOADEVENT_NOSUCHFILE			4
   1631 //#define DOWNLOADEVENT_LOCALFILEOPENFAILED	5
   1632 //#define DOWNLOADEVENT_TCPERROR			6
   1633 //#define DOWNLOADEVENT_DISCONNECTERROR		7
   1634 
   1635 	bFlagError = true;
   1636 	return S_OK;
   1637 }
   1638 
   1639 //***********************************************************************************************
   1640 STDMETHODIMP RADownloadEventSink::OnProgressUpdate( int bytesread, int totalsize, int timetaken, int timeleft )
   1641 {
   1642 //	debugprint( ">>> OnProgressUpdate\n" );
   1643 	bFlagProgressUpdate = true;
   1644 
   1645 	iBytesRead = bytesread;
   1646 	iTotalSize = totalsize;
   1647 	iTimeTaken = timetaken;
   1648 	iTimeLeft = timeleft;
   1649 
   1650 	return S_OK;
   1651 }
   1652 
   1653 //***********************************************************************************************
   1654 STDMETHODIMP RADownloadEventSink::OnStatusUpdate( int status )
   1655 {
   1656 //	debugprint( ">>> OnStatusUpdate, status = %i\n", status );
   1657 	bFlagStatusUpdate = true;
   1658 
   1659 	iStatus = status;
   1660 
   1661 	return S_OK;
   1662 }
   1663 
   1664 //***********************************************************************************************
   1665 // Just tell the FTP module to go ahead and resume
   1666 //
   1667 STDMETHODIMP RADownloadEventSink::OnQueryResume()
   1668 {
   1669 //	debugprint( ">>> OnQueryResume\n" );
   1670 	bFlagQueryResume = true;
   1671 
   1672 	bResumed = true;
   1673 
   1674 	return DOWNLOADEVENT_RESUME;
   1675 }
   1676 
   1677 
   1678 //***********************************************************************************************
   1679 //***********************************************************************************************
   1680 RANetUtilEventSink::RANetUtilEventSink( WolapiObject* pOwnerIn ) : m_cRef( 0 ), 	//	init the reference count
   1681 	pOwner( pOwnerIn ),
   1682 	pLadderList( NULL ),
   1683 	pLadderTail( NULL ),
   1684 	pLadderListAM( NULL ),
   1685 	pLadderTailAM( NULL )
   1686 {
   1687 //	debugprint( "RANetUtilEventSink constructor\n" );
   1688 }
   1689 
   1690 //***********************************************************************************************
   1691 RANetUtilEventSink::~RANetUtilEventSink()
   1692 {
   1693 //	debugprint( "RANetUtilEventSink destructor\n" );
   1694 	DeleteLadderList();
   1695 }
   1696 
   1697 //			Interface IUnknown Methods
   1698 //***********************************************************************************************
   1699 // QueryInterface
   1700 //
   1701 HRESULT __stdcall
   1702 RANetUtilEventSink::QueryInterface(const IID& iid, void** ppv)
   1703 {
   1704 //	debugprint( "RANetUtilEventSink::QueryInterface\n" );
   1705 	if ((iid == IID_IUnknown) ||(iid == IID_INetUtilEvent))
   1706 	{
   1707 		*ppv = (INetUtilEvent*)this;		//	Removed static_cast<> ajw
   1708 	}
   1709 	else
   1710 	{
   1711 		*ppv = NULL;
   1712 		return E_NOINTERFACE;
   1713 	}
   1714 	((IUnknown*)(*ppv))->AddRef();		//	Removed reinterpret_cast<> ajw
   1715 	return S_OK ;
   1716 }
   1717 
   1718 //***********************************************************************************************
   1719 // AddRef
   1720 //
   1721 ULONG __stdcall
   1722 RANetUtilEventSink::AddRef()
   1723 {
   1724 //	debugprint( "RANetUtilEventSink::AddRef\n" );
   1725 	return InterlockedIncrement(&m_cRef) ;
   1726 }
   1727 
   1728 //***********************************************************************************************
   1729 // Release
   1730 //
   1731 ULONG __stdcall
   1732 RANetUtilEventSink::Release()
   1733 {
   1734 //	debugprint( "RANetUtilEventSink::Release\n" );
   1735 	if (InterlockedDecrement(&m_cRef) == 0)
   1736 	{
   1737 		delete this ;
   1738 		return 0 ;
   1739 	}
   1740 	return m_cRef;
   1741 }
   1742 
   1743 //***********************************************************************************************
   1744 STDMETHODIMP RANetUtilEventSink::OnGameresSent( HRESULT hRes )
   1745 {
   1746 //	debugprint( ">>> OnGameresSent got: " );
   1747 	DebugChatDef( hRes );
   1748 	return S_OK;
   1749 }
   1750 
   1751 //***********************************************************************************************
   1752 STDMETHODIMP RANetUtilEventSink::OnLadderList( HRESULT hRes, Ladder* pLadderListIn, int /*totalCount*/, long /*timeStamp*/, int /*keyRung*/ )
   1753 {
   1754 	//	Maintenance of ladders list is like that for channels list above.
   1755 	//	DeleteLadderList();		-> This is done once, before a set of RequestLadderList() calls are made.
   1756 //	debugprint( ">>> OnLadderList got: " );
   1757 	DebugChatDef( hRes );
   1758 
   1759 	if( SUCCEEDED( hRes ) )
   1760 	{
   1761 //		debugprint( "(SUCCEEDED)\n" );
   1762 		//	Copy ladder list to our own list.
   1763 		while( pLadderListIn )
   1764 		{
   1765 //			debugprint( "OnLadderList got %s, rung %u\n", pLadderListIn->login_name, pLadderListIn->rung );
   1766 			if( *pLadderListIn->login_name != 0 && pLadderListIn->rung != -1 )
   1767 			{
   1768 				Ladder* pLadderNew = new Ladder;
   1769 				*pLadderNew = *pLadderListIn;
   1770 				pLadderNew->next = NULL;			//	(We don't want the value that was just copied!)
   1771 				if( pLadderNew->sku == LADDER_CODE_RA )
   1772 				{
   1773 					if( !pLadderTail )
   1774 					{
   1775 						//	First Ladder in list.
   1776 						pLadderList = pLadderNew;		//	This is the head of our Ladder list.
   1777 						pLadderTail = pLadderNew;
   1778 					}
   1779 					else
   1780 					{
   1781 						pLadderTail->next = pLadderNew;
   1782 						pLadderTail = pLadderNew;
   1783 					}
   1784 					if( _stricmp( (char*)pLadderNew->login_name, pOwner->szMyName ) == 0 )
   1785 					{
   1786 						//	Set up local player's win/loss string.
   1787 						sprintf( pOwner->szMyRecord, TXT_WOL_PERSONALWINLOSSRECORD, pOwner->szMyName, 
   1788 									pLadderNew->rung, pLadderNew->wins, pLadderNew->losses, pLadderNew->points );
   1789 						pOwner->bMyRecordUpdated = true;
   1790 					}
   1791 				}
   1792 				else		//	sku must be LADDER_CODE_AM
   1793 				{
   1794 					if( !pLadderTailAM )
   1795 					{
   1796 						//	First Ladder in list.
   1797 						pLadderListAM = pLadderNew;		//	This is the head of our Ladder list.
   1798 						pLadderTailAM = pLadderNew;
   1799 					}
   1800 					else
   1801 					{
   1802 						pLadderTailAM->next = pLadderNew;
   1803 						pLadderTailAM = pLadderNew;
   1804 					}
   1805 					if( _stricmp( (char*)pLadderNew->login_name, pOwner->szMyName ) == 0 )
   1806 					{
   1807 						//	Set up local player's win/loss string for Aftermath.
   1808 						sprintf( pOwner->szMyRecordAM, TXT_WOL_PERSONALWINLOSSRECORDAM, pOwner->szMyName, 
   1809 									pLadderNew->rung, pLadderNew->wins, pLadderNew->losses, pLadderNew->points );
   1810 						pOwner->bMyRecordUpdated = true;
   1811 					}
   1812 				}
   1813 			}
   1814 			pLadderListIn = pLadderListIn->next;
   1815 		}
   1816 		//	Update shown list.
   1817 		pOwner->ListChannelUsers();
   1818 	}
   1819 
   1820 	return S_OK;
   1821 }
   1822 
   1823 //***********************************************************************************************
   1824 STDMETHODIMP RANetUtilEventSink::OnPing( HRESULT hRes, int time, unsigned long ip, int /*handle*/ )
   1825 {
   1826 	if( pOwner->bDoingDisconnectPinging )
   1827 	{
   1828 //		debugprint( ">>> OnPing got : ip %i, time %i, ", ip, time );
   1829 		DebugChatDef( hRes );
   1830 
   1831 		if( ip == pOwner->TournamentOpponentIP )
   1832 		{
   1833 			//	This is the result of the opponent ping.
   1834 			if( time != -1 )
   1835 				pOwner->DisconnectPingResult_Opponent[ pOwner->iDisconnectPingCurrent ] = PING_GOOD;
   1836 			else
   1837 				pOwner->DisconnectPingResult_Opponent[ pOwner->iDisconnectPingCurrent ] = PING_BAD;
   1838 //			debugprint( "Set ping #%i for Opponent\n", pOwner->iDisconnectPingCurrent );
   1839 		}
   1840 		else
   1841 		{
   1842 			//	This is the result of the game server ping.
   1843 			if( time != -1 )
   1844 				pOwner->DisconnectPingResult_Server[ pOwner->iDisconnectPingCurrent ] = PING_GOOD;
   1845 			else
   1846 				pOwner->DisconnectPingResult_Server[ pOwner->iDisconnectPingCurrent ] = PING_BAD;
   1847 //			debugprint( "Set ping #%i for Server\n", pOwner->iDisconnectPingCurrent );
   1848 		}
   1849 	}
   1850 	return S_OK;
   1851 }
   1852 
   1853 //***********************************************************************************************
   1854 void RANetUtilEventSink::DeleteLadderList()
   1855 {
   1856 //	debugprint( "DeleteLadderList()\n" );
   1857 	//	Delete all Ladders allocated on the heap.
   1858 	while( pLadderList )
   1859 	{
   1860 		Ladder* pLadderHead = pLadderList;
   1861 		pLadderList = pLadderHead->next;
   1862 		delete pLadderHead;
   1863 	}
   1864 	pLadderTail = NULL;
   1865 }
   1866 
   1867 //***********************************************************************************************
   1868 unsigned int RANetUtilEventSink::GetUserRank( const char* szName, bool bRankRA )
   1869 {
   1870 	//	Searches for szName in ladder list, returns player rank if found, else 0.
   1871 	//	Slow linear search.
   1872 	//	If bRankRA, returns RA rank, else returns AM rank.
   1873 //	debugprint( "GetUserRank: Asked for %s, ", szName );
   1874 	Ladder* pLad;
   1875 	if( bRankRA )
   1876 		pLad = pLadderList;
   1877 	else
   1878 		pLad = pLadderListAM;
   1879 
   1880 	while( pLad )
   1881 	{
   1882 //		debugprint( "  comparing %s\n", (char*)pLad->login_name );
   1883 		if( _stricmp( (char*)pLad->login_name, szName ) == 0 )
   1884 		{
   1885 //			debugprint( "found rung value %u\n", pLad->rung );
   1886 			return pLad->rung;
   1887 		}
   1888 		pLad = pLad->next;
   1889 	}
   1890 //	debugprint( "couldn't find in my ladder list.\n", szName );
   1891 
   1892 	return 0;
   1893 }
   1894 
   1895 //***********************************************************************************************
   1896 //***********************************************************************************************
   1897 void ChatDefAsText( char* szDesc, HRESULT hRes )
   1898 {
   1899 	//	Sets szDesc to the text meaning of hRes.
   1900 	//	Make sure szDesc is as long as the longest of the below.
   1901 	switch( hRes )
   1902 	{
   1903 	case CHAT_E_NICKINUSE       :
   1904 		sprintf( szDesc, "Your nick is still logged into chat" );
   1905 		break;
   1906 	case CHAT_E_BADPASS         :
   1907 		sprintf( szDesc, "Your password is incorrect during login" );
   1908 		break;
   1909 	case CHAT_E_NONESUCH		   :
   1910 		sprintf( szDesc, "Reference made to non-existant user or channel" );
   1911 		break;
   1912 	case CHAT_E_CON_NETDOWN        :
   1913 		sprintf( szDesc, "The network layer is down or cannot be initialized for some reason" );
   1914 		break;
   1915 	case CHAT_E_CON_LOOKUP_FAILED  :
   1916 		sprintf( szDesc, "Name lookup (e.g DNS) failed for some reason" );
   1917 		break;
   1918 	case CHAT_E_CON_ERROR          :
   1919 		sprintf( szDesc, "Some fatal error occured with the net connection" );
   1920 		break;
   1921 	case CHAT_E_TIMEOUT            :
   1922 		sprintf( szDesc, "General request timeout for a request" );
   1923 		break;
   1924 	case CHAT_E_MUSTPATCH        :
   1925 		sprintf( szDesc, "Must patch before continuing" );
   1926 		break;
   1927 	case CHAT_E_STATUSERROR		:
   1928 		sprintf( szDesc, "Miscellaneous internal status error" );
   1929 		break;
   1930 	case CHAT_E_UNKNOWNRESPONSE	:
   1931 		sprintf( szDesc, "Server has returned something we don't recognise" );
   1932 		break;
   1933 	case CHAT_E_CHANNELFULL		:
   1934 		sprintf( szDesc, "Tried to join a channel that has enough players already" );
   1935 		break;
   1936 	case CHAT_E_CHANNELEXISTS	:
   1937 		sprintf( szDesc, "Tried to create a channel that already exists" );
   1938 		break;
   1939 	case CHAT_E_CHANNELDOESNOTEXIST		:
   1940 		sprintf( szDesc, "Tried to join a channel that does not exist" );
   1941 		break;
   1942 	case CHAT_E_BADCHANNELPASSWORD		:
   1943 		sprintf( szDesc, "Tried to join a channel with the wrong password" );
   1944 		break;
   1945 	case CHAT_E_BANNED            :
   1946 		sprintf( szDesc, "You've been banned from the server / channel" );
   1947 		break;
   1948 	case CHAT_E_NOT_OPER           :
   1949 		sprintf( szDesc, "You tried to do something that required operator status" );
   1950 		break;
   1951 
   1952 	case CHAT_S_CON_CONNECTING     :
   1953 		sprintf( szDesc, "A network connection is underway" );
   1954 		break;
   1955 	case CHAT_S_CON_CONNECTED      :
   1956 		sprintf( szDesc, "A network connection is complete" );
   1957 		break;
   1958 	case CHAT_S_CON_DISCONNECTING  :
   1959 		sprintf( szDesc, "A network connection is going down" );
   1960 		break;
   1961 	case CHAT_S_CON_DISCONNECTED   :
   1962 		sprintf( szDesc, "A network connection is closed" );
   1963 		break;
   1964 	case CHAT_S_FIND_NOTHERE   :
   1965 		sprintf( szDesc, "Find - Nick not in system" );
   1966 		break;
   1967 	case CHAT_S_FIND_NOCHAN   :
   1968 		sprintf( szDesc, "Find - Not in any channels" );
   1969 		break;
   1970 	case CHAT_S_FIND_OFF   :
   1971 		sprintf( szDesc, "Find - user has find turned off" );
   1972 		break;
   1973 	case CHAT_S_PAGE_NOTHERE   :
   1974 		sprintf( szDesc, "Page - Nick not in system" );
   1975 		break;
   1976 	case CHAT_S_PAGE_OFF   :
   1977 		sprintf( szDesc, "Page - user has page turned off" );
   1978 		break;
   1979 	case CHAT_E_NOTCONNECTED    :
   1980 		sprintf( szDesc, "You are not connected to the chat server" );
   1981 		break;
   1982 	case CHAT_E_NOCHANNEL       :
   1983 		sprintf( szDesc, "You are not in a channel" );
   1984 		break;
   1985 	case CHAT_E_NOTIMPLEMENTED  :
   1986 		sprintf( szDesc, "Feature is not implemented" );
   1987 		break;
   1988 	case CHAT_E_PENDINGREQUEST  :
   1989 		sprintf( szDesc, "The request was made while while a conflicting request was still pending" );
   1990 		break;
   1991 	case CHAT_E_PARAMERROR	   :
   1992 		sprintf( szDesc, "Invalid parameter passed - usually a NULL pointer" );
   1993 		break;
   1994 	case CHAT_E_LEAVECHANNEL		:
   1995 		sprintf( szDesc, "Tried to create or join a channel before leaving the previous one" );
   1996 		break;
   1997 	case CHAT_E_JOINCHANNEL		:
   1998 		sprintf( szDesc, "Tried to send something to a channel when not a member of any channel" );
   1999 		break;
   2000 	case CHAT_E_UNKNOWNCHANNEL	:
   2001 		sprintf( szDesc, "Tried to join a non-existant channel" );
   2002 		break;
   2003 	case S_OK:
   2004 		sprintf( szDesc, "S_OK" );
   2005 		break;
   2006 	case E_FAIL:
   2007 		sprintf( szDesc, "E_FAIL" );
   2008 		break;
   2009 	default:
   2010 		sprintf( szDesc, "ERROR - Value not recognized!" );
   2011 		break;
   2012 	}
   2013 	//	Append NetUtil errors.
   2014 	switch( hRes )
   2015 	{
   2016 	case NETUTIL_E_ERROR:
   2017 		strcat( szDesc, "  NetUtil: NETUTIL_E_ERROR" );
   2018 		break;
   2019 	case NETUTIL_E_BUSY:
   2020 		strcat( szDesc, "  NetUtil: NETUTIL_E_BUSY"  );
   2021 		break;
   2022 	case NETUTIL_S_FINISHED:
   2023 		strcat( szDesc, "  NetUtil: NETUTIL_S_FINISHED" );
   2024 		break;
   2025 	}
   2026 }
   2027 
   2028 //***********************************************************************************************
   2029 void DebugChatDef( HRESULT hRes )
   2030 {
   2031 	char szText[200];
   2032 	ChatDefAsText( szText, hRes );
   2033 //	debugprint( "%s\n", szText );
   2034 }
   2035 
   2036 //***********************************************************************************************
   2037 int iChannelLobbyNumber( const unsigned char* szChannelName )
   2038 {
   2039 	//	Returns lobby number of channel, or -1 for "channel is not a lobby".
   2040 	if( strncmp( (char*)szChannelName, LOB_PREFIX, strlen( LOB_PREFIX ) ) == 0 )
   2041 	{
   2042 		char szNum[10];
   2043 		strcpy( szNum, (char*)szChannelName + strlen( LOB_PREFIX ) );
   2044 //		debugprint( " ^ iChannelLobbyNumber returning atoi of %s\n", szNum );
   2045 		return atoi( szNum );
   2046 	}
   2047 	else
   2048 		return -1;
   2049 }
   2050 
   2051 //***********************************************************************************************
   2052 void InterpretLobbyNumber( char* szLobbyNameToSet, int iLobby )
   2053 {
   2054 	//	Hard-coded translation of lobby number to apparent lobby name.
   2055 	switch( iLobby )
   2056 	{
   2057 	case 0:
   2058 		strcpy( szLobbyNameToSet, "Combat Alley" );
   2059 		break;
   2060 	case 1:
   2061 		strcpy( szLobbyNameToSet, "No Man's Land" );
   2062 		break;
   2063 	case 2:
   2064 		strcpy( szLobbyNameToSet, "Hell's Pass" );
   2065 		break;
   2066 	case 3:
   2067 		strcpy( szLobbyNameToSet, "Lost Vegas" );
   2068 		break;
   2069 	case 4:
   2070 		strcpy( szLobbyNameToSet, "Death Valley" );
   2071 		break;
   2072 	case 5:
   2073 		strcpy( szLobbyNameToSet, "The Wastelands" );
   2074 		break;
   2075 	case 6:
   2076 		strcpy( szLobbyNameToSet, "Isle of Fury" );
   2077 		break;
   2078 	case 7:
   2079 		strcpy( szLobbyNameToSet, "Armourgarden" );
   2080 		break;
   2081 	case 8:
   2082 		strcpy( szLobbyNameToSet, "The Hive" );
   2083 		break;
   2084 	case 9:
   2085 		strcpy( szLobbyNameToSet, "North by Northwest" );
   2086 		break;
   2087 	case 10:
   2088 		strcpy( szLobbyNameToSet, "Decatur High" );
   2089 		break;
   2090 	case 11:
   2091 		strcpy( szLobbyNameToSet, "Damnation Alley" );
   2092 		break;
   2093 	default:
   2094 		sprintf( szLobbyNameToSet, "%ith Division", iLobby );
   2095 		break;
   2096 	}
   2097 }
   2098 
   2099 #endif