/ [w3loader] / ghost / src / ghost.cpp
To checkout: svn checkout http://svn.gnu.org.ua/sources/w3loader/ghost/src/ghost.cpp
Puszcza

Contents of /ghost/src/ghost.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 5 - (show annotations)
Tue Jun 2 19:44:46 2020 UTC (17 months, 4 weeks ago) by zombieteam
File size: 54734 byte(s)
kick when save game
1 /*
2
3 Copyright [2008] [Trevor Hogan]
4
5 Licensed under the Apache License, Version 2.0 (the "License");
6 you may not use this file except in compliance with the License.
7 You may obtain a copy of the License at
8
9 http://www.apache.org/licenses/LICENSE-2.0
10
11 Unless required by applicable law or agreed to in writing, software
12 distributed under the License is distributed on an "AS IS" BASIS,
13 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 See the License for the specific language governing permissions and
15 limitations under the License.
16
17 CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/
18
19 */
20
21 #include "ghost.h"
22 #include "util.h"
23 #include "crc32.h"
24 #include "sha1.h"
25 #include "csvparser.h"
26 #include "config.h"
27 #include "language.h"
28 #include "socket.h"
29 #include "ghostdb.h"
30 #include "ghostdbsqlite.h"
31 #include "ghostdbmysql.h"
32 #include "bnet.h"
33 #include "map.h"
34 #include "packed.h"
35 #include "savegame.h"
36 #include "gameplayer.h"
37 #include "gameprotocol.h"
38 #include "gpsprotocol.h"
39 #include "game_base.h"
40 #include "game.h"
41 #include "game_admin.h"
42
43 #include <signal.h>
44 #include <stdlib.h>
45
46 #ifdef WIN32
47 #include <ws2tcpip.h> // for WSAIoctl
48 #endif
49
50 #define __STORMLIB_SELF__
51 #include <stormlib/StormLib.h>
52
53 /*
54
55 #include "ghost.h"
56 #include "util.h"
57 #include "crc32.h"
58 #include "sha1.h"
59 #include "csvparser.h"
60 #include "config.h"
61 #include "language.h"
62 #include "socket.h"
63 #include "commandpacket.h"
64 #include "ghostdb.h"
65 #include "ghostdbsqlite.h"
66 #include "ghostdbmysql.h"
67 #include "bncsutilinterface.h"
68 #include "warden.h"
69 #include "bnlsprotocol.h"
70 #include "bnlsclient.h"
71 #include "bnetprotocol.h"
72 #include "bnet.h"
73 #include "map.h"
74 #include "packed.h"
75 #include "savegame.h"
76 #include "replay.h"
77 #include "gameslot.h"
78 #include "gameplayer.h"
79 #include "gameprotocol.h"
80 #include "gpsprotocol.h"
81 #include "game_base.h"
82 #include "game.h"
83 #include "game_admin.h"
84 #include "stats.h"
85 #include "statsdota.h"
86 #include "sqlite3.h"
87
88 */
89
90 #ifdef WIN32
91 #include <windows.h>
92 #include <winsock.h>
93 #endif
94
95 #include <time.h>
96
97 #ifndef WIN32
98 #include <sys/time.h>
99 #endif
100
101 #ifdef __APPLE__
102 #include <mach/mach_time.h>
103 #endif
104
105 string gCFGFile;
106 string gLogFile;
107 uint32_t gLogMethod;
108 ofstream *gLog = NULL;
109 CGHost *gGHost = NULL;
110
111 uint32_t GetTime( )
112 {
113 return GetTicks( ) / 1000;
114 }
115
116 uint32_t GetTicks( )
117 {
118 #ifdef WIN32
119 // don't use GetTickCount anymore because it's not accurate enough (~16ms resolution)
120 // don't use QueryPerformanceCounter anymore because it isn't guaranteed to be strictly increasing on some systems and thus requires "smoothing" code
121 // use timeGetTime instead, which typically has a high resolution (5ms or more) but we request a lower resolution on startup
122
123 return timeGetTime( );
124 #elif __APPLE__
125 uint64_t current = mach_absolute_time( );
126 static mach_timebase_info_data_t info = { 0, 0 };
127 // get timebase info
128 if( info.denom == 0 )
129 mach_timebase_info( &info );
130 uint64_t elapsednano = current * ( info.numer / info.denom );
131 // convert ns to ms
132 return elapsednano / 1e6;
133 #else
134 uint32_t ticks;
135 struct timespec t;
136 clock_gettime( CLOCK_MONOTONIC, &t );
137 ticks = t.tv_sec * 1000;
138 ticks += t.tv_nsec / 1000000;
139 return ticks;
140 #endif
141 }
142
143 void SignalCatcher2( int s )
144 {
145 CONSOLE_Print( "[!!!] caught signal " + UTIL_ToString( s ) + ", exiting NOW" );
146
147 if( gGHost )
148 {
149 if( gGHost->m_Exiting )
150 exit( 1 );
151 else
152 gGHost->m_Exiting = true;
153 }
154 else
155 exit( 1 );
156 }
157
158 void SignalCatcher( int s )
159 {
160 // signal( SIGABRT, SignalCatcher2 );
161 signal( SIGINT, SignalCatcher2 );
162
163 CONSOLE_Print( "[!!!] caught signal " + UTIL_ToString( s ) + ", exiting nicely" );
164
165 if( gGHost )
166 gGHost->m_ExitingNice = true;
167 else
168 exit( 1 );
169 }
170
171 void CONSOLE_Print( string message )
172 {
173 cout << message << endl;
174
175 // logging
176
177 if( !gLogFile.empty( ) )
178 {
179 if( gLogMethod == 1 )
180 {
181 ofstream Log;
182 Log.open( gLogFile.c_str( ), ios :: app );
183
184 if( !Log.fail( ) )
185 {
186 time_t Now = time( NULL );
187 string Time = asctime( localtime( &Now ) );
188
189 // erase the newline
190
191 Time.erase( Time.size( ) - 1 );
192 Log << "[" << Time << "] " << message << endl;
193 Log.close( );
194 }
195 }
196 else if( gLogMethod == 2 )
197 {
198 if( gLog && !gLog->fail( ) )
199 {
200 time_t Now = time( NULL );
201 string Time = asctime( localtime( &Now ) );
202
203 // erase the newline
204
205 Time.erase( Time.size( ) - 1 );
206 *gLog << "[" << Time << "] " << message << endl;
207 gLog->flush( );
208 }
209 }
210 }
211 }
212
213 void DEBUG_Print( string message )
214 {
215 cout << message << endl;
216 }
217
218 void DEBUG_Print( BYTEARRAY b )
219 {
220 cout << "{ ";
221
222 for( unsigned int i = 0; i < b.size( ); i++ )
223 cout << hex << (int)b[i] << " ";
224
225 cout << "}" << endl;
226 }
227
228 //
229 // main
230 //
231
232 int main( int argc, char **argv )
233 {
234 gCFGFile = "ghost.cfg";
235
236 if( argc > 1 && argv[1] )
237 gCFGFile = argv[1];
238
239 // read config file
240
241 CConfig CFG;
242 CFG.Read( "default.cfg" );
243 CFG.Read( gCFGFile );
244 gLogFile = CFG.GetString( "bot_log", string( ) );
245 gLogMethod = CFG.GetInt( "bot_logmethod", 1 );
246
247 if( !gLogFile.empty( ) )
248 {
249 if( gLogMethod == 1 )
250 {
251 // log method 1: open, append, and close the log for every message
252 // this works well on Linux but poorly on Windows, particularly as the log file grows in size
253 // the log file can be edited/moved/deleted while GHost++ is running
254 }
255 else if( gLogMethod == 2 )
256 {
257 // log method 2: open the log on startup, flush the log for every message, close the log on shutdown
258 // the log file CANNOT be edited/moved/deleted while GHost++ is running
259
260 gLog = new ofstream( );
261 gLog->open( gLogFile.c_str( ), ios :: app );
262 }
263 }
264
265 CONSOLE_Print( "[GHOST] starting up" );
266
267 if( !gLogFile.empty( ) )
268 {
269 if( gLogMethod == 1 )
270 CONSOLE_Print( "[GHOST] using log method 1, logging is enabled and [" + gLogFile + "] will not be locked" );
271 else if( gLogMethod == 2 )
272 {
273 if( gLog->fail( ) )
274 CONSOLE_Print( "[GHOST] using log method 2 but unable to open [" + gLogFile + "] for appending, logging is disabled" );
275 else
276 CONSOLE_Print( "[GHOST] using log method 2, logging is enabled and [" + gLogFile + "] is now locked" );
277 }
278 }
279 else
280 CONSOLE_Print( "[GHOST] no log file specified, logging is disabled" );
281
282 // catch SIGABRT and SIGINT
283
284 // signal( SIGABRT, SignalCatcher );
285 signal( SIGINT, SignalCatcher );
286
287 #ifndef WIN32
288 // disable SIGPIPE since some systems like OS X don't define MSG_NOSIGNAL
289
290 signal( SIGPIPE, SIG_IGN );
291 #endif
292
293 #ifdef WIN32
294 // initialize timer resolution
295 // attempt to set the resolution as low as possible from 1ms to 5ms
296
297 unsigned int TimerResolution = 0;
298
299 for( unsigned int i = 1; i <= 5; i++ )
300 {
301 if( timeBeginPeriod( i ) == TIMERR_NOERROR )
302 {
303 TimerResolution = i;
304 break;
305 }
306 else if( i < 5 )
307 CONSOLE_Print( "[GHOST] error setting Windows timer resolution to " + UTIL_ToString( i ) + " milliseconds, trying a higher resolution" );
308 else
309 {
310 CONSOLE_Print( "[GHOST] error setting Windows timer resolution" );
311 return 1;
312 }
313 }
314
315 CONSOLE_Print( "[GHOST] using Windows timer with resolution " + UTIL_ToString( TimerResolution ) + " milliseconds" );
316 #elif __APPLE__
317 // not sure how to get the resolution
318 #else
319 // print the timer resolution
320
321 struct timespec Resolution;
322
323 if( clock_getres( CLOCK_MONOTONIC, &Resolution ) == -1 )
324 CONSOLE_Print( "[GHOST] error getting monotonic timer resolution" );
325 else
326 CONSOLE_Print( "[GHOST] using monotonic timer with resolution " + UTIL_ToString( (double)( Resolution.tv_nsec / 1000 ), 2 ) + " microseconds" );
327 #endif
328
329 #ifdef WIN32
330 // initialize winsock
331
332 CONSOLE_Print( "[GHOST] starting winsock" );
333 WSADATA wsadata;
334
335 if( WSAStartup( MAKEWORD( 2, 2 ), &wsadata ) != 0 )
336 {
337 CONSOLE_Print( "[GHOST] error starting winsock" );
338 return 1;
339 }
340
341 // increase process priority
342
343 CONSOLE_Print( "[GHOST] setting process priority to \"above normal\"" );
344 SetPriorityClass( GetCurrentProcess( ), ABOVE_NORMAL_PRIORITY_CLASS );
345 #endif
346
347 // initialize ghost
348
349 gGHost = new CGHost( &CFG );
350
351 while( 1 )
352 {
353 // block for 50ms on all sockets - if you intend to perform any timed actions more frequently you should change this
354 // that said it's likely we'll loop more often than this due to there being data waiting on one of the sockets but there aren't any guarantees
355
356 if( gGHost->Update( 50000 ) )
357 break;
358 }
359
360 // shutdown ghost
361
362 CONSOLE_Print( "[GHOST] shutting down" );
363 delete gGHost;
364 gGHost = NULL;
365
366 #ifdef WIN32
367 // shutdown winsock
368
369 CONSOLE_Print( "[GHOST] shutting down winsock" );
370 WSACleanup( );
371
372 // shutdown timer
373
374 timeEndPeriod( TimerResolution );
375 #endif
376
377 if( gLog )
378 {
379 if( !gLog->fail( ) )
380 gLog->close( );
381
382 delete gLog;
383 }
384
385 return 0;
386 }
387
388 //
389 // CGHost
390 //
391
392 CGHost :: CGHost( CConfig *CFG )
393 {
394 m_UDPSocket = new CUDPSocket( );
395 m_UDPSocket->SetBroadcastTarget( CFG->GetString( "udp_broadcasttarget", string( ) ) );
396 m_UDPSocket->SetDontRoute( CFG->GetInt( "udp_dontroute", 0 ) == 0 ? false : true );
397 m_ReconnectSocket = NULL;
398 m_GPSProtocol = new CGPSProtocol( );
399 m_CRC = new CCRC32( );
400 m_CRC->Initialize( );
401 m_SHA = new CSHA1( );
402 m_CurrentGame = NULL;
403 string DBType = CFG->GetString( "db_type", "sqlite3" );
404 CONSOLE_Print( "[GHOST] opening primary database" );
405
406 if( DBType == "mysql" )
407 {
408 #ifdef GHOST_MYSQL
409 m_DB = new CGHostDBMySQL( CFG );
410 #else
411 CONSOLE_Print( "[GHOST] warning - this binary was not compiled with MySQL database support, using SQLite database instead" );
412 m_DB = new CGHostDBSQLite( CFG );
413 #endif
414 }
415 else
416 m_DB = new CGHostDBSQLite( CFG );
417
418 CONSOLE_Print( "[GHOST] opening secondary (local) database" );
419 m_DBLocal = new CGHostDBSQLite( CFG );
420
421 // get a list of local IP addresses
422 // this list is used elsewhere to determine if a player connecting to the bot is local or not
423
424 CONSOLE_Print( "[GHOST] attempting to find local IP addresses" );
425
426 #ifdef WIN32
427 // use a more reliable Windows specific method since the portable method doesn't always work properly on Windows
428 // code stolen from: http://tangentsoft.net/wskfaq/examples/getifaces.html
429
430 SOCKET sd = WSASocket( AF_INET, SOCK_DGRAM, 0, 0, 0, 0 );
431
432 if( sd == SOCKET_ERROR )
433 CONSOLE_Print( "[GHOST] error finding local IP addresses - failed to create socket (error code " + UTIL_ToString( WSAGetLastError( ) ) + ")" );
434 else
435 {
436 INTERFACE_INFO InterfaceList[20];
437 unsigned long nBytesReturned;
438
439 if( WSAIoctl( sd, SIO_GET_INTERFACE_LIST, 0, 0, &InterfaceList, sizeof(InterfaceList), &nBytesReturned, 0, 0 ) == SOCKET_ERROR )
440 CONSOLE_Print( "[GHOST] error finding local IP addresses - WSAIoctl failed (error code " + UTIL_ToString( WSAGetLastError( ) ) + ")" );
441 else
442 {
443 int nNumInterfaces = nBytesReturned / sizeof(INTERFACE_INFO);
444
445 for( int i = 0; i < nNumInterfaces; i++ )
446 {
447 sockaddr_in *pAddress;
448 pAddress = (sockaddr_in *)&(InterfaceList[i].iiAddress);
449 CONSOLE_Print( "[GHOST] local IP address #" + UTIL_ToString( i + 1 ) + " is [" + string( inet_ntoa( pAddress->sin_addr ) ) + "]" );
450 m_LocalAddresses.push_back( UTIL_CreateByteArray( (uint32_t)pAddress->sin_addr.s_addr, false ) );
451 }
452 }
453
454 closesocket( sd );
455 }
456 #else
457 // use a portable method
458
459 char HostName[255];
460
461 if( gethostname( HostName, 255 ) == SOCKET_ERROR )
462 CONSOLE_Print( "[GHOST] error finding local IP addresses - failed to get local hostname" );
463 else
464 {
465 CONSOLE_Print( "[GHOST] local hostname is [" + string( HostName ) + "]" );
466 struct hostent *HostEnt = gethostbyname( HostName );
467
468 if( !HostEnt )
469 CONSOLE_Print( "[GHOST] error finding local IP addresses - gethostbyname failed" );
470 else
471 {
472 for( int i = 0; HostEnt->h_addr_list[i] != NULL; i++ )
473 {
474 struct in_addr Address;
475 memcpy( &Address, HostEnt->h_addr_list[i], sizeof(struct in_addr) );
476 CONSOLE_Print( "[GHOST] local IP address #" + UTIL_ToString( i + 1 ) + " is [" + string( inet_ntoa( Address ) ) + "]" );
477 m_LocalAddresses.push_back( UTIL_CreateByteArray( (uint32_t)Address.s_addr, false ) );
478 }
479 }
480 }
481 #endif
482
483 m_Language = NULL;
484 m_Exiting = false;
485 m_ExitingNice = false;
486 m_Enabled = true;
487 m_Version = "17.0";
488 m_HostCounter = 1;
489 m_AutoHostMaximumGames = CFG->GetInt( "autohost_maxgames", 0 );
490 m_AutoHostAutoStartPlayers = CFG->GetInt( "autohost_startplayers", 0 );
491 m_AutoHostGameName = CFG->GetString( "autohost_gamename", string( ) );
492 m_AutoHostOwner = CFG->GetString( "autohost_owner", string( ) );
493 m_LastAutoHostTime = GetTime( );
494 m_AutoHostMatchMaking = false;
495 m_AutoHostMinimumScore = 0.0;
496 m_AutoHostMaximumScore = 0.0;
497 m_AllGamesFinished = false;
498 m_AllGamesFinishedTime = 0;
499 m_TFT = CFG->GetInt( "bot_tft", 1 ) == 0 ? false : true;
500
501 if( m_TFT )
502 CONSOLE_Print( "[GHOST] acting as Warcraft III: The Frozen Throne" );
503 else
504 CONSOLE_Print( "[GHOST] acting as Warcraft III: Reign of Chaos" );
505
506 m_HostPort = CFG->GetInt( "bot_hostport", 6112 );
507 m_Reconnect = CFG->GetInt( "bot_reconnect", 1 ) == 0 ? false : true;
508 m_ReconnectPort = CFG->GetInt( "bot_reconnectport", 6114 );
509 m_DefaultMap = CFG->GetString( "bot_defaultmap", "map" );
510 m_AdminGameCreate = CFG->GetInt( "admingame_create", 0 ) == 0 ? false : true;
511 m_AdminGamePort = CFG->GetInt( "admingame_port", 6113 );
512 m_AdminGamePassword = CFG->GetString( "admingame_password", string( ) );
513 m_AdminGameMap = CFG->GetString( "admingame_map", string( ) );
514 m_LANWar3Version = CFG->GetInt( "lan_war3version", 24 );
515 m_ReplayWar3Version = CFG->GetInt( "replay_war3version", 24 );
516 m_ReplayBuildNumber = CFG->GetInt( "replay_buildnumber", 6059 );
517 SetConfigs( CFG );
518
519 // load the battle.net connections
520 // we're just loading the config data and creating the CBNET classes here, the connections are established later (in the Update function)
521
522 for( uint32_t i = 1; i < 10; i++ )
523 {
524 string Prefix;
525
526 if( i == 1 )
527 Prefix = "bnet_";
528 else
529 Prefix = "bnet" + UTIL_ToString( i ) + "_";
530
531 string Server = CFG->GetString( Prefix + "server", string( ) );
532 string ServerAlias = CFG->GetString( Prefix + "serveralias", string( ) );
533 string CDKeyROC = CFG->GetString( Prefix + "cdkeyroc", string( ) );
534 string CDKeyTFT = CFG->GetString( Prefix + "cdkeytft", string( ) );
535 string CountryAbbrev = CFG->GetString( Prefix + "countryabbrev", "USA" );
536 string Country = CFG->GetString( Prefix + "country", "United States" );
537 string Locale = CFG->GetString( Prefix + "locale", "system" );
538 uint32_t LocaleID;
539
540 if( Locale == "system" )
541 {
542 #ifdef WIN32
543 LocaleID = GetUserDefaultLangID( );
544 #else
545 LocaleID = 1033;
546 #endif
547 }
548 else
549 LocaleID = UTIL_ToUInt32( Locale );
550
551 string UserName = CFG->GetString( Prefix + "username", string( ) );
552 string UserPassword = CFG->GetString( Prefix + "password", string( ) );
553 string FirstChannel = CFG->GetString( Prefix + "firstchannel", "The Void" );
554 string RootAdmin = CFG->GetString( Prefix + "rootadmin", string( ) );
555 string BNETCommandTrigger = CFG->GetString( Prefix + "commandtrigger", "!" );
556
557 if( BNETCommandTrigger.empty( ) )
558 BNETCommandTrigger = "!";
559
560 bool HoldFriends = CFG->GetInt( Prefix + "holdfriends", 1 ) == 0 ? false : true;
561 bool HoldClan = CFG->GetInt( Prefix + "holdclan", 1 ) == 0 ? false : true;
562 bool PublicCommands = CFG->GetInt( Prefix + "publiccommands", 1 ) == 0 ? false : true;
563 string BNLSServer = CFG->GetString( Prefix + "bnlsserver", string( ) );
564 int BNLSPort = CFG->GetInt( Prefix + "bnlsport", 9367 );
565 int BNLSWardenCookie = CFG->GetInt( Prefix + "bnlswardencookie", 0 );
566 unsigned char War3Version = CFG->GetInt( Prefix + "custom_war3version", 24 );
567 BYTEARRAY EXEVersion = UTIL_ExtractNumbers( CFG->GetString( Prefix + "custom_exeversion", string( ) ), 4 );
568 BYTEARRAY EXEVersionHash = UTIL_ExtractNumbers( CFG->GetString( Prefix + "custom_exeversionhash", string( ) ), 4 );
569 string PasswordHashType = CFG->GetString( Prefix + "custom_passwordhashtype", string( ) );
570 string PVPGNRealmName = CFG->GetString( Prefix + "custom_pvpgnrealmname", "PvPGN Realm" );
571 uint32_t MaxMessageLength = CFG->GetInt( Prefix + "custom_maxmessagelength", 200 );
572 string War3Path = UTIL_AddPathSeperator( CFG->GetString( Prefix + "war3path", m_Warcraft3Path ) );
573
574 if( Server.empty( ) )
575 break;
576
577 if( CDKeyROC.empty( ) )
578 {
579 CONSOLE_Print( "[GHOST] missing " + Prefix + "cdkeyroc, skipping this battle.net connection" );
580 continue;
581 }
582
583 if( m_TFT && CDKeyTFT.empty( ) )
584 {
585 CONSOLE_Print( "[GHOST] missing " + Prefix + "cdkeytft, skipping this battle.net connection" );
586 continue;
587 }
588
589 if( UserName.empty( ) )
590 {
591 CONSOLE_Print( "[GHOST] missing " + Prefix + "username, skipping this battle.net connection" );
592 continue;
593 }
594
595 if( UserPassword.empty( ) )
596 {
597 CONSOLE_Print( "[GHOST] missing " + Prefix + "password, skipping this battle.net connection" );
598 continue;
599 }
600
601 CONSOLE_Print( "[GHOST] found battle.net connection #" + UTIL_ToString( i ) + " for server [" + Server + "]" );
602
603 if( Locale == "system" )
604 {
605 #ifdef WIN32
606 CONSOLE_Print( "[GHOST] using system locale of " + UTIL_ToString( LocaleID ) );
607 #else
608 CONSOLE_Print( "[GHOST] unable to get system locale, using default locale of 1033" );
609 #endif
610 }
611
612 m_BNETs.push_back( new CBNET( this, Server, ServerAlias, BNLSServer, (uint16_t)BNLSPort, (uint32_t)BNLSWardenCookie, CDKeyROC, CDKeyTFT, CountryAbbrev, Country, LocaleID, UserName, UserPassword, FirstChannel, RootAdmin, BNETCommandTrigger[0], HoldFriends, HoldClan, PublicCommands, War3Version, EXEVersion, EXEVersionHash, PasswordHashType, PVPGNRealmName, MaxMessageLength, i, War3Path ) );
613 }
614
615 if( m_BNETs.empty( ) )
616 CONSOLE_Print( "[GHOST] warning - no battle.net connections found in config file" );
617
618 // extract common.j and blizzard.j from War3Patch.mpq if we can
619 // these two files are necessary for calculating "map_crc" when loading maps so we make sure to do it before loading the default map
620 // see CMap :: Load for more information
621
622 ExtractScripts( );
623
624 // load the default maps (note: make sure to run ExtractScripts first)
625
626 if( m_DefaultMap.size( ) < 4 || m_DefaultMap.substr( m_DefaultMap.size( ) - 4 ) != ".cfg" )
627 {
628 m_DefaultMap += ".cfg";
629 CONSOLE_Print( "[GHOST] adding \".cfg\" to default map -> new default is [" + m_DefaultMap + "]" );
630 }
631
632 CConfig MapCFG;
633 MapCFG.Read( m_MapCFGPath + m_DefaultMap );
634 m_Map = new CMap( this, &MapCFG, m_MapCFGPath + m_DefaultMap );
635
636 if( !m_AdminGameMap.empty( ) )
637 {
638 if( m_AdminGameMap.size( ) < 4 || m_AdminGameMap.substr( m_AdminGameMap.size( ) - 4 ) != ".cfg" )
639 {
640 m_AdminGameMap += ".cfg";
641 CONSOLE_Print( "[GHOST] adding \".cfg\" to default admin game map -> new default is [" + m_AdminGameMap + "]" );
642 }
643
644 CONSOLE_Print( "[GHOST] trying to load default admin game map" );
645 CConfig AdminMapCFG;
646 AdminMapCFG.Read( m_MapCFGPath + m_AdminGameMap );
647 m_AdminMap = new CMap( this, &AdminMapCFG, m_MapCFGPath + m_AdminGameMap );
648
649 if( !m_AdminMap->GetValid( ) )
650 {
651 CONSOLE_Print( "[GHOST] default admin game map isn't valid, using hardcoded admin game map instead" );
652 delete m_AdminMap;
653 m_AdminMap = new CMap( this );
654 }
655 }
656 else
657 {
658 CONSOLE_Print( "[GHOST] using hardcoded admin game map" );
659 m_AdminMap = new CMap( this );
660 }
661
662 m_AutoHostMap = new CMap( *m_Map );
663 m_SaveGame = new CSaveGame( );
664
665 // load the iptocountry data
666
667 LoadIPToCountryData( );
668
669 // create the admin game
670
671 if( m_AdminGameCreate )
672 {
673 CONSOLE_Print( "[GHOST] creating admin game" );
674 m_AdminGame = new CAdminGame( this, m_AdminMap, NULL, m_AdminGamePort, 0, "GHost++ Admin Game", m_AdminGamePassword );
675
676 if( m_AdminGamePort == m_HostPort )
677 CONSOLE_Print( "[GHOST] warning - admingame_port and bot_hostport are set to the same value, you won't be able to host any games" );
678 }
679 else
680 m_AdminGame = NULL;
681
682 if( m_BNETs.empty( ) && !m_AdminGame )
683 CONSOLE_Print( "[GHOST] warning - no battle.net connections found and no admin game created" );
684
685 #ifdef GHOST_MYSQL
686 CONSOLE_Print( "[GHOST] GHost++ Version " + m_Version + " (with MySQL support)" );
687 #else
688 CONSOLE_Print( "[GHOST] GHost++ Version " + m_Version + " (without MySQL support)" );
689 #endif
690 }
691
692 CGHost :: ~CGHost( )
693 {
694 delete m_UDPSocket;
695 delete m_ReconnectSocket;
696
697 for( vector<CTCPSocket *> :: iterator i = m_ReconnectSockets.begin( ); i != m_ReconnectSockets.end( ); i++ )
698 delete *i;
699
700 delete m_GPSProtocol;
701 delete m_CRC;
702 delete m_SHA;
703
704 for( vector<CBNET *> :: iterator i = m_BNETs.begin( ); i != m_BNETs.end( ); i++ )
705 delete *i;
706
707 delete m_CurrentGame;
708 delete m_AdminGame;
709
710 for( vector<CBaseGame *> :: iterator i = m_Games.begin( ); i != m_Games.end( ); i++ )
711 delete *i;
712
713 delete m_DB;
714 delete m_DBLocal;
715
716 // warning: we don't delete any entries of m_Callables here because we can't be guaranteed that the associated threads have terminated
717 // this is fine if the program is currently exiting because the OS will clean up after us
718 // but if you try to recreate the CGHost object within a single session you will probably leak resources!
719
720 if( !m_Callables.empty( ) )
721 CONSOLE_Print( "[GHOST] warning - " + UTIL_ToString( m_Callables.size( ) ) + " orphaned callables were leaked (this is not an error)" );
722
723 delete m_Language;
724 delete m_Map;
725 delete m_AdminMap;
726 delete m_AutoHostMap;
727 delete m_SaveGame;
728 }
729
730 bool CGHost :: Update( long usecBlock )
731 {
732 // todotodo: do we really want to shutdown if there's a database error? is there any way to recover from this?
733
734 if( m_DB->HasError( ) )
735 {
736 CONSOLE_Print( "[GHOST] database error - " + m_DB->GetError( ) );
737 return true;
738 }
739
740 if( m_DBLocal->HasError( ) )
741 {
742 CONSOLE_Print( "[GHOST] local database error - " + m_DBLocal->GetError( ) );
743 return true;
744 }
745
746 // try to exit nicely if requested to do so
747
748 if( m_ExitingNice )
749 {
750 if( !m_BNETs.empty( ) )
751 {
752 CONSOLE_Print( "[GHOST] deleting all battle.net connections in preparation for exiting nicely" );
753
754 for( vector<CBNET *> :: iterator i = m_BNETs.begin( ); i != m_BNETs.end( ); i++ )
755 delete *i;
756
757 m_BNETs.clear( );
758 }
759
760 if( m_CurrentGame )
761 {
762 CONSOLE_Print( "[GHOST] deleting current game in preparation for exiting nicely" );
763 delete m_CurrentGame;
764 m_CurrentGame = NULL;
765 }
766
767 if( m_AdminGame )
768 {
769 CONSOLE_Print( "[GHOST] deleting admin game in preparation for exiting nicely" );
770 delete m_AdminGame;
771 m_AdminGame = NULL;
772 }
773
774 if( m_Games.empty( ) )
775 {
776 if( !m_AllGamesFinished )
777 {
778 CONSOLE_Print( "[GHOST] all games finished, waiting 60 seconds for threads to finish" );
779 CONSOLE_Print( "[GHOST] there are " + UTIL_ToString( m_Callables.size( ) ) + " threads in progress" );
780 m_AllGamesFinished = true;
781 m_AllGamesFinishedTime = GetTime( );
782 }
783 else
784 {
785 if( m_Callables.empty( ) )
786 {
787 CONSOLE_Print( "[GHOST] all threads finished, exiting nicely" );
788 m_Exiting = true;
789 }
790 else if( GetTime( ) - m_AllGamesFinishedTime >= 60 )
791 {
792 CONSOLE_Print( "[GHOST] waited 60 seconds for threads to finish, exiting anyway" );
793 CONSOLE_Print( "[GHOST] there are " + UTIL_ToString( m_Callables.size( ) ) + " threads still in progress which will be terminated" );
794 m_Exiting = true;
795 }
796 }
797 }
798 }
799
800 // update callables
801
802 for( vector<CBaseCallable *> :: iterator i = m_Callables.begin( ); i != m_Callables.end( ); )
803 {
804 if( (*i)->GetReady( ) )
805 {
806 m_DB->RecoverCallable( *i );
807 delete *i;
808 i = m_Callables.erase( i );
809 }
810 else
811 i++;
812 }
813
814 // create the GProxy++ reconnect listener
815
816 if( m_Reconnect )
817 {
818 if( !m_ReconnectSocket )
819 {
820 m_ReconnectSocket = new CTCPServer( );
821
822 if( m_ReconnectSocket->Listen( m_BindAddress, m_ReconnectPort ) )
823 CONSOLE_Print( "[GHOST] listening for GProxy++ reconnects on port " + UTIL_ToString( m_ReconnectPort ) );
824 else
825 {
826 CONSOLE_Print( "[GHOST] error listening for GProxy++ reconnects on port " + UTIL_ToString( m_ReconnectPort ) );
827 delete m_ReconnectSocket;
828 m_ReconnectSocket = NULL;
829 m_Reconnect = false;
830 }
831 }
832 else if( m_ReconnectSocket->HasError( ) )
833 {
834 CONSOLE_Print( "[GHOST] GProxy++ reconnect listener error (" + m_ReconnectSocket->GetErrorString( ) + ")" );
835 delete m_ReconnectSocket;
836 m_ReconnectSocket = NULL;
837 m_Reconnect = false;
838 }
839 }
840
841 unsigned int NumFDs = 0;
842
843 // take every socket we own and throw it in one giant select statement so we can block on all sockets
844
845 int nfds = 0;
846 fd_set fd;
847 fd_set send_fd;
848 FD_ZERO( &fd );
849 FD_ZERO( &send_fd );
850
851 // 1. all battle.net sockets
852
853 for( vector<CBNET *> :: iterator i = m_BNETs.begin( ); i != m_BNETs.end( ); i++ )
854 NumFDs += (*i)->SetFD( &fd, &send_fd, &nfds );
855
856 // 2. the current game's server and player sockets
857
858 if( m_CurrentGame )
859 NumFDs += m_CurrentGame->SetFD( &fd, &send_fd, &nfds );
860
861 // 3. the admin game's server and player sockets
862
863 if( m_AdminGame )
864 NumFDs += m_AdminGame->SetFD( &fd, &send_fd, &nfds );
865
866 // 4. all running games' player sockets
867
868 for( vector<CBaseGame *> :: iterator i = m_Games.begin( ); i != m_Games.end( ); i++ )
869 NumFDs += (*i)->SetFD( &fd, &send_fd, &nfds );
870
871 // 5. the GProxy++ reconnect socket(s)
872
873 if( m_Reconnect && m_ReconnectSocket )
874 {
875 m_ReconnectSocket->SetFD( &fd, &send_fd, &nfds );
876 NumFDs++;
877 }
878
879 for( vector<CTCPSocket *> :: iterator i = m_ReconnectSockets.begin( ); i != m_ReconnectSockets.end( ); i++ )
880 {
881 (*i)->SetFD( &fd, &send_fd, &nfds );
882 NumFDs++;
883 }
884
885 // before we call select we need to determine how long to block for
886 // previously we just blocked for a maximum of the passed usecBlock microseconds
887 // however, in an effort to make game updates happen closer to the desired latency setting we now use a dynamic block interval
888 // note: we still use the passed usecBlock as a hard maximum
889
890 for( vector<CBaseGame *> :: iterator i = m_Games.begin( ); i != m_Games.end( ); i++ )
891 {
892 if( (*i)->GetNextTimedActionTicks( ) * 1000 < usecBlock )
893 usecBlock = (*i)->GetNextTimedActionTicks( ) * 1000;
894 }
895
896 // always block for at least 1ms just in case something goes wrong
897 // this prevents the bot from sucking up all the available CPU if a game keeps asking for immediate updates
898 // it's a bit ridiculous to include this check since, in theory, the bot is programmed well enough to never make this mistake
899 // however, considering who programmed it, it's worthwhile to do it anyway
900
901 if( usecBlock < 1000 )
902 usecBlock = 1000;
903
904 struct timeval tv;
905 tv.tv_sec = 0;
906 tv.tv_usec = usecBlock;
907
908 struct timeval send_tv;
909 send_tv.tv_sec = 0;
910 send_tv.tv_usec = 0;
911
912 #ifdef WIN32
913 select( 1, &fd, NULL, NULL, &tv );
914 select( 1, NULL, &send_fd, NULL, &send_tv );
915 #else
916 select( nfds + 1, &fd, NULL, NULL, &tv );
917 select( nfds + 1, NULL, &send_fd, NULL, &send_tv );
918 #endif
919
920 if( NumFDs == 0 )
921 {
922 // we don't have any sockets (i.e. we aren't connected to battle.net maybe due to a lost connection and there aren't any games running)
923 // select will return immediately and we'll chew up the CPU if we let it loop so just sleep for 50ms to kill some time
924
925 MILLISLEEP( 50 );
926 }
927
928 bool AdminExit = false;
929 bool BNETExit = false;
930
931 // update current game
932
933 if( m_CurrentGame )
934 {
935 if( m_CurrentGame->Update( &fd, &send_fd ) )
936 {
937 CONSOLE_Print( "[GHOST] deleting current game [" + m_CurrentGame->GetGameName( ) + "]" );
938 delete m_CurrentGame;
939 m_CurrentGame = NULL;
940
941 for( vector<CBNET *> :: iterator i = m_BNETs.begin( ); i != m_BNETs.end( ); i++ )
942 {
943 (*i)->QueueGameUncreate( );
944 (*i)->QueueEnterChat( );
945 }
946 }
947 else if( m_CurrentGame )
948 m_CurrentGame->UpdatePost( &send_fd );
949 }
950
951 // update admin game
952
953 if( m_AdminGame )
954 {
955 if( m_AdminGame->Update( &fd, &send_fd ) )
956 {
957 CONSOLE_Print( "[GHOST] deleting admin game" );
958 delete m_AdminGame;
959 m_AdminGame = NULL;
960 AdminExit = true;
961 }
962 else if( m_AdminGame )
963 m_AdminGame->UpdatePost( &send_fd );
964 }
965
966 // update running games
967
968 for( vector<CBaseGame *> :: iterator i = m_Games.begin( ); i != m_Games.end( ); )
969 {
970 if( (*i)->Update( &fd, &send_fd ) )
971 {
972 CONSOLE_Print( "[GHOST] deleting game [" + (*i)->GetGameName( ) + "]" );
973 EventGameDeleted( *i );
974 delete *i;
975 i = m_Games.erase( i );
976 }
977 else
978 {
979 (*i)->UpdatePost( &send_fd );
980 i++;
981 }
982 }
983
984 // update battle.net connections
985
986 for( vector<CBNET *> :: iterator i = m_BNETs.begin( ); i != m_BNETs.end( ); i++ )
987 {
988 if( (*i)->Update( &fd, &send_fd ) )
989 BNETExit = true;
990 }
991
992 // update GProxy++ reliable reconnect sockets
993
994 if( m_Reconnect && m_ReconnectSocket )
995 {
996 CTCPSocket *NewSocket = m_ReconnectSocket->Accept( &fd );
997
998 if( NewSocket )
999 m_ReconnectSockets.push_back( NewSocket );
1000 }
1001
1002 for( vector<CTCPSocket *> :: iterator i = m_ReconnectSockets.begin( ); i != m_ReconnectSockets.end( ); )
1003 {
1004 if( (*i)->HasError( ) || !(*i)->GetConnected( ) || GetTime( ) - (*i)->GetLastRecv( ) >= 10 )
1005 {
1006 delete *i;
1007 i = m_ReconnectSockets.erase( i );
1008 continue;
1009 }
1010
1011 (*i)->DoRecv( &fd );
1012 string *RecvBuffer = (*i)->GetBytes( );
1013 BYTEARRAY Bytes = UTIL_CreateByteArray( (unsigned char *)RecvBuffer->c_str( ), RecvBuffer->size( ) );
1014
1015 // a packet is at least 4 bytes
1016
1017 if( Bytes.size( ) >= 4 )
1018 {
1019 if( Bytes[0] == GPS_HEADER_CONSTANT )
1020 {
1021 // bytes 2 and 3 contain the length of the packet
1022
1023 uint16_t Length = UTIL_ByteArrayToUInt16( Bytes, false, 2 );
1024
1025 if( Length >= 4 )
1026 {
1027 if( Bytes.size( ) >= Length )
1028 {
1029 if( Bytes[1] == CGPSProtocol :: GPS_RECONNECT && Length == 13 )
1030 {
1031 unsigned char PID = Bytes[4];
1032 uint32_t ReconnectKey = UTIL_ByteArrayToUInt32( Bytes, false, 5 );
1033 uint32_t LastPacket = UTIL_ByteArrayToUInt32( Bytes, false, 9 );
1034
1035 // look for a matching player in a running game
1036
1037 CGamePlayer *Match = NULL;
1038
1039 for( vector<CBaseGame *> :: iterator j = m_Games.begin( ); j != m_Games.end( ); j++ )
1040 {
1041 if( (*j)->GetGameLoaded( ) )
1042 {
1043 CGamePlayer *Player = (*j)->GetPlayerFromPID( PID );
1044
1045 if( Player && Player->GetGProxy( ) && Player->GetGProxyReconnectKey( ) == ReconnectKey )
1046 {
1047 Match = Player;
1048 break;
1049 }
1050 }
1051 }
1052
1053 if( Match )
1054 {
1055 // reconnect successful!
1056
1057 *RecvBuffer = RecvBuffer->substr( Length );
1058 Match->EventGProxyReconnect( *i, LastPacket );
1059 i = m_ReconnectSockets.erase( i );
1060 continue;
1061 }
1062 else
1063 {
1064 (*i)->PutBytes( m_GPSProtocol->SEND_GPSS_REJECT( REJECTGPS_NOTFOUND ) );
1065 (*i)->DoSend( &send_fd );
1066 delete *i;
1067 i = m_ReconnectSockets.erase( i );
1068 continue;
1069 }
1070 }
1071 else
1072 {
1073 (*i)->PutBytes( m_GPSProtocol->SEND_GPSS_REJECT( REJECTGPS_INVALID ) );
1074 (*i)->DoSend( &send_fd );
1075 delete *i;
1076 i = m_ReconnectSockets.erase( i );
1077 continue;
1078 }
1079 }
1080 }
1081 else
1082 {
1083 (*i)->PutBytes( m_GPSProtocol->SEND_GPSS_REJECT( REJECTGPS_INVALID ) );
1084 (*i)->DoSend( &send_fd );
1085 delete *i;
1086 i = m_ReconnectSockets.erase( i );
1087 continue;
1088 }
1089 }
1090 else
1091 {
1092 (*i)->PutBytes( m_GPSProtocol->SEND_GPSS_REJECT( REJECTGPS_INVALID ) );
1093 (*i)->DoSend( &send_fd );
1094 delete *i;
1095 i = m_ReconnectSockets.erase( i );
1096 continue;
1097 }
1098 }
1099
1100 (*i)->DoSend( &send_fd );
1101 i++;
1102 }
1103
1104 // autohost
1105
1106 if( !m_AutoHostGameName.empty( ) && m_AutoHostMaximumGames != 0 && m_AutoHostAutoStartPlayers != 0 && GetTime( ) - m_LastAutoHostTime >= 30 )
1107 {
1108 // copy all the checks from CGHost :: CreateGame here because we don't want to spam the chat when there's an error
1109 // instead we fail silently and try again soon
1110
1111 if( !m_ExitingNice && m_Enabled && !m_CurrentGame && m_Games.size( ) < m_MaxGames && m_Games.size( ) < m_AutoHostMaximumGames )
1112 {
1113 if( m_AutoHostMap->GetValid( ) )
1114 {
1115 string GameName = m_AutoHostGameName + " #" + UTIL_ToString( m_HostCounter );
1116
1117 if( GameName.size( ) <= 31 )
1118 {
1119 CreateGame( m_AutoHostMap, GAME_PUBLIC, false, GameName, m_AutoHostOwner, m_AutoHostOwner, m_AutoHostServer, false );
1120
1121 if( m_CurrentGame )
1122 {
1123 m_CurrentGame->SetAutoStartPlayers( m_AutoHostAutoStartPlayers );
1124
1125 if( m_AutoHostMatchMaking )
1126 {
1127 if( !m_Map->GetMapMatchMakingCategory( ).empty( ) )
1128 {
1129 if( !( m_Map->GetMapOptions( ) & MAPOPT_FIXEDPLAYERSETTINGS ) )
1130 CONSOLE_Print( "[GHOST] autohostmm - map_matchmakingcategory [" + m_Map->GetMapMatchMakingCategory( ) + "] found but matchmaking can only be used with fixed player settings, matchmaking disabled" );
1131 else
1132 {
1133 CONSOLE_Print( "[GHOST] autohostmm - map_matchmakingcategory [" + m_Map->GetMapMatchMakingCategory( ) + "] found, matchmaking enabled" );
1134
1135 m_CurrentGame->SetMatchMaking( true );
1136 m_CurrentGame->SetMinimumScore( m_AutoHostMinimumScore );
1137 m_CurrentGame->SetMaximumScore( m_AutoHostMaximumScore );
1138 }
1139 }
1140 else
1141 CONSOLE_Print( "[GHOST] autohostmm - map_matchmakingcategory not found, matchmaking disabled" );
1142 }
1143 }
1144 }
1145 else
1146 {
1147 CONSOLE_Print( "[GHOST] stopped auto hosting, next game name [" + GameName + "] is too long (the maximum is 31 characters)" );
1148 m_AutoHostGameName.clear( );
1149 m_AutoHostOwner.clear( );
1150 m_AutoHostServer.clear( );
1151 m_AutoHostMaximumGames = 0;
1152 m_AutoHostAutoStartPlayers = 0;
1153 m_AutoHostMatchMaking = false;
1154 m_AutoHostMinimumScore = 0.0;
1155 m_AutoHostMaximumScore = 0.0;
1156 }
1157 }
1158 else
1159 {
1160 CONSOLE_Print( "[GHOST] stopped auto hosting, map config file [" + m_AutoHostMap->GetCFGFile( ) + "] is invalid" );
1161 m_AutoHostGameName.clear( );
1162 m_AutoHostOwner.clear( );
1163 m_AutoHostServer.clear( );
1164 m_AutoHostMaximumGames = 0;
1165 m_AutoHostAutoStartPlayers = 0;
1166 m_AutoHostMatchMaking = false;
1167 m_AutoHostMinimumScore = 0.0;
1168 m_AutoHostMaximumScore = 0.0;
1169 }
1170 }
1171
1172 m_LastAutoHostTime = GetTime( );
1173 }
1174
1175 return m_Exiting || AdminExit || BNETExit;
1176 }
1177
1178 void CGHost :: EventBNETConnecting( CBNET *bnet )
1179 {
1180 if( m_AdminGame )
1181 m_AdminGame->SendAllChat( m_Language->ConnectingToBNET( bnet->GetServer( ) ) );
1182
1183 if( m_CurrentGame )
1184 m_CurrentGame->SendAllChat( m_Language->ConnectingToBNET( bnet->GetServer( ) ) );
1185 }
1186
1187 void CGHost :: EventBNETConnected( CBNET *bnet )
1188 {
1189 if( m_AdminGame )
1190 m_AdminGame->SendAllChat( m_Language->ConnectedToBNET( bnet->GetServer( ) ) );
1191
1192 if( m_CurrentGame )
1193 m_CurrentGame->SendAllChat( m_Language->ConnectedToBNET( bnet->GetServer( ) ) );
1194 }
1195
1196 void CGHost :: EventBNETDisconnected( CBNET *bnet )
1197 {
1198 if( m_AdminGame )
1199 m_AdminGame->SendAllChat( m_Language->DisconnectedFromBNET( bnet->GetServer( ) ) );
1200
1201 if( m_CurrentGame )
1202 m_CurrentGame->SendAllChat( m_Language->DisconnectedFromBNET( bnet->GetServer( ) ) );
1203 }
1204
1205 void CGHost :: EventBNETLoggedIn( CBNET *bnet )
1206 {
1207 if( m_AdminGame )
1208 m_AdminGame->SendAllChat( m_Language->LoggedInToBNET( bnet->GetServer( ) ) );
1209
1210 if( m_CurrentGame )
1211 m_CurrentGame->SendAllChat( m_Language->LoggedInToBNET( bnet->GetServer( ) ) );
1212 }
1213
1214 void CGHost :: EventBNETGameRefreshed( CBNET *bnet )
1215 {
1216 if( m_AdminGame )
1217 m_AdminGame->SendAllChat( m_Language->BNETGameHostingSucceeded( bnet->GetServer( ) ) );
1218
1219 if( m_CurrentGame )
1220 m_CurrentGame->EventGameRefreshed( bnet->GetServer( ) );
1221 }
1222
1223 void CGHost :: EventBNETGameRefreshFailed( CBNET *bnet )
1224 {
1225 if( m_CurrentGame )
1226 {
1227 for( vector<CBNET *> :: iterator i = m_BNETs.begin( ); i != m_BNETs.end( ); i++ )
1228 {
1229 (*i)->QueueChatCommand( m_Language->UnableToCreateGameTryAnotherName( bnet->GetServer( ), m_CurrentGame->GetGameName( ) ) );
1230
1231 if( (*i)->GetServer( ) == m_CurrentGame->GetCreatorServer( ) )
1232 (*i)->QueueChatCommand( m_Language->UnableToCreateGameTryAnotherName( bnet->GetServer( ), m_CurrentGame->GetGameName( ) ), m_CurrentGame->GetCreatorName( ), true );
1233 }
1234
1235 if( m_AdminGame )
1236 m_AdminGame->SendAllChat( m_Language->BNETGameHostingFailed( bnet->GetServer( ), m_CurrentGame->GetGameName( ) ) );
1237
1238 m_CurrentGame->SendAllChat( m_Language->UnableToCreateGameTryAnotherName( bnet->GetServer( ), m_CurrentGame->GetGameName( ) ) );
1239
1240 // we take the easy route and simply close the lobby if a refresh fails
1241 // it's possible at least one refresh succeeded and therefore the game is still joinable on at least one battle.net (plus on the local network) but we don't keep track of that
1242 // we only close the game if it has no players since we support game rehosting (via !priv and !pub in the lobby)
1243
1244 if( m_CurrentGame->GetNumHumanPlayers( ) == 0 )
1245 m_CurrentGame->SetExiting( true );
1246
1247 m_CurrentGame->SetRefreshError( true );
1248 }
1249 }
1250
1251 void CGHost :: EventBNETConnectTimedOut( CBNET *bnet )
1252 {
1253 if( m_AdminGame )
1254 m_AdminGame->SendAllChat( m_Language->ConnectingToBNETTimedOut( bnet->GetServer( ) ) );
1255
1256 if( m_CurrentGame )
1257 m_CurrentGame->SendAllChat( m_Language->ConnectingToBNETTimedOut( bnet->GetServer( ) ) );
1258 }
1259
1260 void CGHost :: EventBNETWhisper( CBNET *bnet, string user, string message )
1261 {
1262 if( m_AdminGame )
1263 {
1264 m_AdminGame->SendAdminChat( "[W: " + bnet->GetServerAlias( ) + "] [" + user + "] " + message );
1265
1266 if( m_CurrentGame )
1267 m_CurrentGame->SendLocalAdminChat( "[W: " + bnet->GetServerAlias( ) + "] [" + user + "] " + message );
1268
1269 for( vector<CBaseGame *> :: iterator i = m_Games.begin( ); i != m_Games.end( ); i++ )
1270 (*i)->SendLocalAdminChat( "[W: " + bnet->GetServerAlias( ) + "] [" + user + "] " + message );
1271 }
1272 }
1273
1274 void CGHost :: EventBNETChat( CBNET *bnet, string user, string message )
1275 {
1276 if( m_AdminGame )
1277 {
1278 m_AdminGame->SendAdminChat( "[L: " + bnet->GetServerAlias( ) + "] [" + user + "] " + message );
1279
1280 if( m_CurrentGame )
1281 m_CurrentGame->SendLocalAdminChat( "[L: " + bnet->GetServerAlias( ) + "] [" + user + "] " + message );
1282
1283 for( vector<CBaseGame *> :: iterator i = m_Games.begin( ); i != m_Games.end( ); i++ )
1284 (*i)->SendLocalAdminChat( "[L: " + bnet->GetServerAlias( ) + "] [" + user + "] " + message );
1285 }
1286 }
1287
1288 void CGHost :: EventBNETEmote( CBNET *bnet, string user, string message )
1289 {
1290 if( m_AdminGame )
1291 {
1292 m_AdminGame->SendAdminChat( "[E: " + bnet->GetServerAlias( ) + "] [" + user + "] " + message );
1293
1294 if( m_CurrentGame )
1295 m_CurrentGame->SendLocalAdminChat( "[E: " + bnet->GetServerAlias( ) + "] [" + user + "] " + message );
1296
1297 for( vector<CBaseGame *> :: iterator i = m_Games.begin( ); i != m_Games.end( ); i++ )
1298 (*i)->SendLocalAdminChat( "[E: " + bnet->GetServerAlias( ) + "] [" + user + "] " + message );
1299 }
1300 }
1301
1302 void CGHost :: EventGameDeleted( CBaseGame *game )
1303 {
1304 for( vector<CBNET *> :: iterator i = m_BNETs.begin( ); i != m_BNETs.end( ); i++ )
1305 {
1306 (*i)->QueueChatCommand( m_Language->GameIsOver( game->GetDescription( ) ) );
1307
1308 if( (*i)->GetServer( ) == game->GetCreatorServer( ) )
1309 (*i)->QueueChatCommand( m_Language->GameIsOver( game->GetDescription( ) ), game->GetCreatorName( ), true );
1310 }
1311 }
1312
1313 void CGHost :: ReloadConfigs( )
1314 {
1315 CConfig CFG;
1316 CFG.Read( "default.cfg" );
1317 CFG.Read( gCFGFile );
1318 SetConfigs( &CFG );
1319 }
1320
1321 void CGHost :: SetConfigs( CConfig *CFG )
1322 {
1323 // this doesn't set EVERY config value since that would potentially require reconfiguring the battle.net connections
1324 // it just set the easily reloadable values
1325
1326 m_LanguageFile = CFG->GetString( "bot_language", "language.cfg" );
1327 delete m_Language;
1328 m_Language = new CLanguage( m_LanguageFile );
1329 m_Warcraft3Path = UTIL_AddPathSeperator( CFG->GetString( "bot_war3path", "C:\\Program Files\\Warcraft III\\" ) );
1330 m_BindAddress = CFG->GetString( "bot_bindaddress", string( ) );
1331 m_ReconnectWaitTime = CFG->GetInt( "bot_reconnectwaittime", 3 );
1332 m_MaxGames = CFG->GetInt( "bot_maxgames", 5 );
1333 string BotCommandTrigger = CFG->GetString( "bot_commandtrigger", "!" );
1334
1335 if( BotCommandTrigger.empty( ) )
1336 BotCommandTrigger = "!";
1337
1338 m_CommandTrigger = BotCommandTrigger[0];
1339 m_MapCFGPath = UTIL_AddPathSeperator( CFG->GetString( "bot_mapcfgpath", string( ) ) );
1340 m_SaveGamePath = UTIL_AddPathSeperator( CFG->GetString( "bot_savegamepath", string( ) ) );
1341 m_MapPath = UTIL_AddPathSeperator( CFG->GetString( "bot_mappath", string( ) ) );
1342 m_SaveReplays = CFG->GetInt( "bot_savereplays", 0 ) == 0 ? false : true;
1343 m_ReplayPath = UTIL_AddPathSeperator( CFG->GetString( "bot_replaypath", string( ) ) );
1344 m_VirtualHostName = CFG->GetString( "bot_virtualhostname", "|cFF4080C0GHost" );
1345 m_HideIPAddresses = CFG->GetInt( "bot_hideipaddresses", 0 ) == 0 ? false : true;
1346 m_CheckMultipleIPUsage = CFG->GetInt( "bot_checkmultipleipusage", 1 ) == 0 ? false : true;
1347
1348 if( m_VirtualHostName.size( ) > 15 )
1349 {
1350 m_VirtualHostName = "|cFF4080C0GHost";
1351 CONSOLE_Print( "[GHOST] warning - bot_virtualhostname is longer than 15 characters, using default virtual host name" );
1352 }
1353
1354 m_SpoofChecks = CFG->GetInt( "bot_spoofchecks", 2 );
1355 m_RequireSpoofChecks = CFG->GetInt( "bot_requirespoofchecks", 0 ) == 0 ? false : true;
1356 m_ReserveAdmins = CFG->GetInt( "bot_reserveadmins", 1 ) == 0 ? false : true;
1357 m_RefreshMessages = CFG->GetInt( "bot_refreshmessages", 0 ) == 0 ? false : true;
1358 m_AutoLock = CFG->GetInt( "bot_autolock", 0 ) == 0 ? false : true;
1359 m_AutoSave = CFG->GetInt( "bot_autosave", 0 ) == 0 ? false : true;
1360 m_AllowDownloads = CFG->GetInt( "bot_allowdownloads", 0 );
1361 m_PingDuringDownloads = CFG->GetInt( "bot_pingduringdownloads", 0 ) == 0 ? false : true;
1362 m_MaxDownloaders = CFG->GetInt( "bot_maxdownloaders", 3 );
1363 m_MaxDownloadSpeed = CFG->GetInt( "bot_maxdownloadspeed", 100 );
1364 m_LCPings = CFG->GetInt( "bot_lcpings", 1 ) == 0 ? false : true;
1365 m_AutoKickPing = CFG->GetInt( "bot_autokickping", 400 );
1366 m_BanMethod = CFG->GetInt( "bot_banmethod", 1 );
1367 m_IPBlackListFile = CFG->GetString( "bot_ipblacklistfile", "ipblacklist.txt" );
1368 m_LobbyTimeLimit = CFG->GetInt( "bot_lobbytimelimit", 10 );
1369 m_Latency = CFG->GetInt( "bot_latency", 100 );
1370 m_SyncLimit = CFG->GetInt( "bot_synclimit", 50 );
1371 m_VoteKickAllowed = CFG->GetInt( "bot_votekickallowed", 1 ) == 0 ? false : true;
1372 m_VoteKickPercentage = CFG->GetInt( "bot_votekickpercentage", 100 );
1373
1374 if( m_VoteKickPercentage > 100 )
1375 {
1376 m_VoteKickPercentage = 100;
1377 CONSOLE_Print( "[GHOST] warning - bot_votekickpercentage is greater than 100, using 100 instead" );
1378 }
1379
1380 m_MOTDFile = CFG->GetString( "bot_motdfile", "motd.txt" );
1381 m_GameLoadedFile = CFG->GetString( "bot_gameloadedfile", "gameloaded.txt" );
1382 m_GameOverFile = CFG->GetString( "bot_gameoverfile", "gameover.txt" );
1383 m_LocalAdminMessages = CFG->GetInt( "bot_localadminmessages", 1 ) == 0 ? false : true;
1384 m_TCPNoDelay = CFG->GetInt( "tcp_nodelay", 0 ) == 0 ? false : true;
1385 m_MatchMakingMethod = CFG->GetInt( "bot_matchmakingmethod", 1 );
1386 m_KickGameSaver = CFG->GetInt( "bot_kickgamesaver", 0 ) == 0 ? false : true;
1387 }
1388
1389 void CGHost :: ExtractScripts( )
1390 {
1391 string PatchMPQFileName = m_Warcraft3Path + "War3Patch.mpq";
1392 HANDLE PatchMPQ;
1393
1394 if( SFileOpenArchive( PatchMPQFileName.c_str( ), 0, MPQ_OPEN_FORCE_MPQ_V1, &PatchMPQ ) )
1395 {
1396 CONSOLE_Print( "[GHOST] loading MPQ file [" + PatchMPQFileName + "]" );
1397 HANDLE SubFile;
1398
1399 // common.j
1400
1401 if( SFileOpenFileEx( PatchMPQ, "Scripts\\common.j", 0, &SubFile ) )
1402 {
1403 uint32_t FileLength = SFileGetFileSize( SubFile, NULL );
1404
1405 if( FileLength > 0 && FileLength != 0xFFFFFFFF )
1406 {
1407 char *SubFileData = new char[FileLength];
1408 DWORD BytesRead = 0;
1409
1410 if( SFileReadFile( SubFile, SubFileData, FileLength, &BytesRead ) )
1411 {
1412 CONSOLE_Print( "[GHOST] extracting Scripts\\common.j from MPQ file to [" + m_MapCFGPath + "common.j]" );
1413 UTIL_FileWrite( m_MapCFGPath + "common.j", (unsigned char *)SubFileData, BytesRead );
1414 }
1415 else
1416 CONSOLE_Print( "[GHOST] warning - unable to extract Scripts\\common.j from MPQ file" );
1417
1418 delete [] SubFileData;
1419 }
1420
1421 SFileCloseFile( SubFile );
1422 }
1423 else
1424 CONSOLE_Print( "[GHOST] couldn't find Scripts\\common.j in MPQ file" );
1425
1426 // blizzard.j
1427
1428 if( SFileOpenFileEx( PatchMPQ, "Scripts\\blizzard.j", 0, &SubFile ) )
1429 {
1430 uint32_t FileLength = SFileGetFileSize( SubFile, NULL );
1431
1432 if( FileLength > 0 && FileLength != 0xFFFFFFFF )
1433 {
1434 char *SubFileData = new char[FileLength];
1435 DWORD BytesRead = 0;
1436
1437 if( SFileReadFile( SubFile, SubFileData, FileLength, &BytesRead ) )
1438 {
1439 CONSOLE_Print( "[GHOST] extracting Scripts\\blizzard.j from MPQ file to [" + m_MapCFGPath + "blizzard.j]" );
1440 UTIL_FileWrite( m_MapCFGPath + "blizzard.j", (unsigned char *)SubFileData, BytesRead );
1441 }
1442 else
1443 CONSOLE_Print( "[GHOST] warning - unable to extract Scripts\\blizzard.j from MPQ file" );
1444
1445 delete [] SubFileData;
1446 }
1447
1448 SFileCloseFile( SubFile );
1449 }
1450 else
1451 CONSOLE_Print( "[GHOST] couldn't find Scripts\\blizzard.j in MPQ file" );
1452
1453 SFileCloseArchive( PatchMPQ );
1454 }
1455 else
1456 CONSOLE_Print( "[GHOST] warning - unable to load MPQ file [" + PatchMPQFileName + "] - error code " + UTIL_ToString( GetLastError( ) ) );
1457 }
1458
1459 void CGHost :: LoadIPToCountryData( )
1460 {
1461 ifstream in;
1462 in.open( "ip-to-country.csv" );
1463
1464 if( in.fail( ) )
1465 CONSOLE_Print( "[GHOST] warning - unable to read file [ip-to-country.csv], iptocountry data not loaded" );
1466 else
1467 {
1468 CONSOLE_Print( "[GHOST] started loading [ip-to-country.csv]" );
1469
1470 // the begin and commit statements are optimizations
1471 // we're about to insert ~4 MB of data into the database so if we allow the database to treat each insert as a transaction it will take a LONG time
1472 // todotodo: handle begin/commit failures a bit more gracefully
1473
1474 if( !m_DBLocal->Begin( ) )
1475 CONSOLE_Print( "[GHOST] warning - failed to begin local database transaction, iptocountry data not loaded" );
1476 else
1477 {
1478 unsigned char Percent = 0;
1479 string Line;
1480 string IP1;
1481 string IP2;
1482 string Country;
1483 CSVParser parser;
1484
1485 // get length of file for the progress meter
1486
1487 in.seekg( 0, ios :: end );
1488 uint32_t FileLength = in.tellg( );
1489 in.seekg( 0, ios :: beg );
1490
1491 while( !in.eof( ) )
1492 {
1493 getline( in, Line );
1494
1495 if( Line.empty( ) )
1496 continue;
1497
1498 parser << Line;
1499 parser >> IP1;
1500 parser >> IP2;
1501 parser >> Country;
1502 m_DBLocal->FromAdd( UTIL_ToUInt32( IP1 ), UTIL_ToUInt32( IP2 ), Country );
1503
1504 // it's probably going to take awhile to load the iptocountry data (~10 seconds on my 3.2 GHz P4 when using SQLite3)
1505 // so let's print a progress meter just to keep the user from getting worried
1506
1507 unsigned char NewPercent = (unsigned char)( (float)in.tellg( ) / FileLength * 100 );
1508
1509 if( NewPercent != Percent && NewPercent % 10 == 0 )
1510 {
1511 Percent = NewPercent;
1512 CONSOLE_Print( "[GHOST] iptocountry data: " + UTIL_ToString( Percent ) + "% loaded" );
1513 }
1514 }
1515
1516 if( !m_DBLocal->Commit( ) )
1517 CONSOLE_Print( "[GHOST] warning - failed to commit local database transaction, iptocountry data not loaded" );
1518 else
1519 CONSOLE_Print( "[GHOST] finished loading [ip-to-country.csv]" );
1520 }
1521
1522 in.close( );
1523 }
1524 }
1525
1526 void CGHost :: CreateGame( CMap *map, unsigned char gameState, bool saveGame, string gameName, string ownerName, string creatorName, string creatorServer, bool whisper )
1527 {
1528 if( !m_Enabled )
1529 {
1530 for( vector<CBNET *> :: iterator i = m_BNETs.begin( ); i != m_BNETs.end( ); i++ )
1531 {
1532 if( (*i)->GetServer( ) == creatorServer )
1533 (*i)->QueueChatCommand( m_Language->UnableToCreateGameDisabled( gameName ), creatorName, whisper );
1534 }
1535
1536 if( m_AdminGame )
1537 m_AdminGame->SendAllChat( m_Language->UnableToCreateGameDisabled( gameName ) );
1538
1539 return;
1540 }
1541
1542 if( gameName.size( ) > 31 )
1543 {
1544 for( vector<CBNET *> :: iterator i = m_BNETs.begin( ); i != m_BNETs.end( ); i++ )
1545 {
1546 if( (*i)->GetServer( ) == creatorServer )
1547 (*i)->QueueChatCommand( m_Language->UnableToCreateGameNameTooLong( gameName ), creatorName, whisper );
1548 }
1549
1550 if( m_AdminGame )
1551 m_AdminGame->SendAllChat( m_Language->UnableToCreateGameNameTooLong( gameName ) );
1552
1553 return;
1554 }
1555
1556 if( !map->GetValid( ) )
1557 {
1558 for( vector<CBNET *> :: iterator i = m_BNETs.begin( ); i != m_BNETs.end( ); i++ )
1559 {
1560 if( (*i)->GetServer( ) == creatorServer )
1561 (*i)->QueueChatCommand( m_Language->UnableToCreateGameInvalidMap( gameName ), creatorName, whisper );
1562 }
1563
1564 if( m_AdminGame )
1565 m_AdminGame->SendAllChat( m_Language->UnableToCreateGameInvalidMap( gameName ) );
1566
1567 return;
1568 }
1569
1570 if( saveGame )
1571 {
1572 if( !m_SaveGame->GetValid( ) )
1573 {
1574 for( vector<CBNET *> :: iterator i = m_BNETs.begin( ); i != m_BNETs.end( ); i++ )
1575 {
1576 if( (*i)->GetServer( ) == creatorServer )
1577 (*i)->QueueChatCommand( m_Language->UnableToCreateGameInvalidSaveGame( gameName ), creatorName, whisper );
1578 }
1579
1580 if( m_AdminGame )
1581 m_AdminGame->SendAllChat( m_Language->UnableToCreateGameInvalidSaveGame( gameName ) );
1582
1583 return;
1584 }
1585
1586 string MapPath1 = m_SaveGame->GetMapPath( );
1587 string MapPath2 = map->GetMapPath( );
1588 transform( MapPath1.begin( ), MapPath1.end( ), MapPath1.begin( ), (int(*)(int))tolower );
1589 transform( MapPath2.begin( ), MapPath2.end( ), MapPath2.begin( ), (int(*)(int))tolower );
1590
1591 if( MapPath1 != MapPath2 )
1592 {
1593 CONSOLE_Print( "[GHOST] path mismatch, saved game path is [" + MapPath1 + "] but map path is [" + MapPath2 + "]" );
1594
1595 for( vector<CBNET *> :: iterator i = m_BNETs.begin( ); i != m_BNETs.end( ); i++ )
1596 {
1597 if( (*i)->GetServer( ) == creatorServer )
1598 (*i)->QueueChatCommand( m_Language->UnableToCreateGameSaveGameMapMismatch( gameName ), creatorName, whisper );
1599 }
1600
1601 if( m_AdminGame )
1602 m_AdminGame->SendAllChat( m_Language->UnableToCreateGameSaveGameMapMismatch( gameName ) );
1603
1604 return;
1605 }
1606
1607 if( m_EnforcePlayers.empty( ) )
1608 {
1609 for( vector<CBNET *> :: iterator i = m_BNETs.begin( ); i != m_BNETs.end( ); i++ )
1610 {
1611 if( (*i)->GetServer( ) == creatorServer )
1612 (*i)->QueueChatCommand( m_Language->UnableToCreateGameMustEnforceFirst( gameName ), creatorName, whisper );
1613 }
1614
1615 if( m_AdminGame )
1616 m_AdminGame->SendAllChat( m_Language->UnableToCreateGameMustEnforceFirst( gameName ) );
1617
1618 return;
1619 }
1620 }
1621
1622 if( m_CurrentGame )
1623 {
1624 for( vector<CBNET *> :: iterator i = m_BNETs.begin( ); i != m_BNETs.end( ); i++ )
1625 {
1626 if( (*i)->GetServer( ) == creatorServer )
1627 (*i)->QueueChatCommand( m_Language->UnableToCreateGameAnotherGameInLobby( gameName, m_CurrentGame->GetDescription( ) ), creatorName, whisper );
1628 }
1629
1630 if( m_AdminGame )
1631 m_AdminGame->SendAllChat( m_Language->UnableToCreateGameAnotherGameInLobby( gameName, m_CurrentGame->GetDescription( ) ) );
1632
1633 return;
1634 }
1635
1636 if( m_Games.size( ) >= m_MaxGames )
1637 {
1638 for( vector<CBNET *> :: iterator i = m_BNETs.begin( ); i != m_BNETs.end( ); i++ )
1639 {
1640 if( (*i)->GetServer( ) == creatorServer )
1641 (*i)->QueueChatCommand( m_Language->UnableToCreateGameMaxGamesReached( gameName, UTIL_ToString( m_MaxGames ) ), creatorName, whisper );
1642 }
1643
1644 if( m_AdminGame )
1645 m_AdminGame->SendAllChat( m_Language->UnableToCreateGameMaxGamesReached( gameName, UTIL_ToString( m_MaxGames ) ) );
1646
1647 return;
1648 }
1649
1650 CONSOLE_Print( "[GHOST] creating game [" + gameName + "]" );
1651
1652 if( saveGame )
1653 m_CurrentGame = new CGame( this, map, m_SaveGame, m_HostPort, gameState, gameName, ownerName, creatorName, creatorServer );
1654 else
1655 m_CurrentGame = new CGame( this, map, NULL, m_HostPort, gameState, gameName, ownerName, creatorName, creatorServer );
1656
1657 // todotodo: check if listening failed and report the error to the user
1658
1659 if( m_SaveGame )
1660 {
1661 m_CurrentGame->SetEnforcePlayers( m_EnforcePlayers );
1662 m_EnforcePlayers.clear( );
1663 }
1664
1665 for( vector<CBNET *> :: iterator i = m_BNETs.begin( ); i != m_BNETs.end( ); i++ )
1666 {
1667 if( whisper && (*i)->GetServer( ) == creatorServer )
1668 {
1669 // note that we send this whisper only on the creator server
1670
1671 if( gameState == GAME_PRIVATE )
1672 (*i)->QueueChatCommand( m_Language->CreatingPrivateGame( gameName, ownerName ), creatorName, whisper );
1673 else if( gameState == GAME_PUBLIC )
1674 (*i)->QueueChatCommand( m_Language->CreatingPublicGame( gameName, ownerName ), creatorName, whisper );
1675 }
1676 else
1677 {
1678 // note that we send this chat message on all other bnet servers
1679
1680 if( gameState == GAME_PRIVATE )
1681 (*i)->QueueChatCommand( m_Language->CreatingPrivateGame( gameName, ownerName ) );
1682 else if( gameState == GAME_PUBLIC )
1683 (*i)->QueueChatCommand( m_Language->CreatingPublicGame( gameName, ownerName ) );
1684 }
1685
1686 if( saveGame )
1687 (*i)->QueueGameCreate( gameState, gameName, string( ), map, m_SaveGame, m_CurrentGame->GetHostCounter( ) );
1688 else
1689 (*i)->QueueGameCreate( gameState, gameName, string( ), map, NULL, m_CurrentGame->GetHostCounter( ) );
1690 }
1691
1692 if( m_AdminGame )
1693 {
1694 if( gameState == GAME_PRIVATE )
1695 m_AdminGame->SendAllChat( m_Language->CreatingPrivateGame( gameName, ownerName ) );
1696 else if( gameState == GAME_PUBLIC )
1697 m_AdminGame->SendAllChat( m_Language->CreatingPublicGame( gameName, ownerName ) );
1698 }
1699
1700 // if we're creating a private game we don't need to send any game refresh messages so we can rejoin the chat immediately
1701 // unfortunately this doesn't work on PVPGN servers because they consider an enterchat message to be a gameuncreate message when in a game
1702 // so don't rejoin the chat if we're using PVPGN
1703
1704 if( gameState == GAME_PRIVATE )
1705 {
1706 for( vector<CBNET *> :: iterator i = m_BNETs.begin( ); i != m_BNETs.end( ); i++ )
1707 {
1708 if( (*i)->GetPasswordHashType( ) != "pvpgn" )
1709 (*i)->QueueEnterChat( );
1710 }
1711 }
1712
1713 // hold friends and/or clan members
1714
1715 for( vector<CBNET *> :: iterator i = m_BNETs.begin( ); i != m_BNETs.end( ); i++ )
1716 {
1717 if( (*i)->GetHoldFriends( ) )
1718 (*i)->HoldFriends( m_CurrentGame );
1719
1720 if( (*i)->GetHoldClan( ) )
1721 (*i)->HoldClan( m_CurrentGame );
1722 }
1723 }

Send suggestions and bug reports to Sergey Poznyakoff
ViewVC Help
Powered by ViewVC 1.1.20