/* ** Command & Conquer Generals Zero Hour(tm) ** Copyright 2025 Electronic Arts Inc. ** ** This program is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program. If not, see . */ #ifdef _WIN32 #include #endif #include #include #include #ifdef _WIN32 #include #else #include #include #endif //#define THREADSAFE_HEADER #include #include #include #include #include #include #include "global.h" #include "generals.h" #include "timezone.h" #include #include #include "mydebug.h" #ifdef _UNIX using namespace std; #else #define sleep(x) Sleep(1000 * (x)) #endif static char *Program_Usage = "A config filename can be given on the command line (default=matchbot.cfg)\n"; void logMonitor(void *); void paranoidLogMonitor(void *); OutputDevice * output_device = NULL; OutputDevice * paranoid_output_device = NULL; void Signal_Quit(int) { INFMSG("Exiting due to signal."); exit(2); } void Setup_Signals(void) { #ifdef _UNIX struct sigaction act, oact; act.sa_handler = Signal_Quit; sigemptyset(&act.sa_mask); act.sa_flags = 0; sigaction(SIGTERM, &act, &oact); sigaction(SIGINT, &act, &oact); #endif } int VerifyFileDescriptors(int requested) { #ifdef _UNIX struct rlimit limit; if (!getrlimit(_SC_OPEN_MAX, &limit)) { INFMSG("Hard limit on file descriptors: " << limit.rlim_max); if (limit.rlim_max < (unsigned int)requested) { ERRMSG("Too many file descriptors requested"); ERRMSG("Hard Limit: " << limit.rlim_max << ", requested: " << requested); requested = limit.rlim_max; } limit.rlim_cur = limit.rlim_max; /* make soft limit the max */ if (setrlimit(_SC_OPEN_MAX, &limit) == -1) { ERRMSG("Error setting max file descriptors to " << limit.rlim_cur); exit(-1); } INFMSG("Soft limit on file descriptors: " << limit.rlim_cur); } else { ERRMSG("Couldn't get limit for _SC_OPEN_MAX"); exit(-1); } #endif return requested; } GeneralsMatcher *s_generalsMatcher = NULL; GeneralsClientMatcher *s_generalsClientMatcher = NULL; int main(int argc, char ** argv) { Wstring config_fname = "matchbot.cfg"; // You can specify the config file on the command line if (argc == 2) config_fname = argv[1]; // Read the config file FILE *fp; if ((fp = fopen(config_fname.get(), "r")) == NULL) { cerr << "\nCan't open the config file '" << config_fname.get() << "'\n\n"; cerr << Program_Usage << endl; exit( -1); } fclose(fp); Global.ReadFile(config_fname.get()); // Setup debugging & logging output Wstring output_file; output_file.set("matchbot.log"); Global.config.getString("OUTPUTFILE", output_file); if (output_file != "STDOUT") { int append = 1; Global.config.getInt("APPENDTOLOG", append); if (append) output_device = new FileD(output_file.get(), "a"); else output_device = new FileD(output_file.get(), "w"); if (!output_device) { cerr << "Could not open " << output_file.get() << " for writing!" << endl; exit( -1); } } else { output_device = new StdoutD; } MsgManager::setAllStreams(output_device); DBGMSG("Matching bot started!"); INFMSG("Matching bot " << argv[0] << " started!"); // Setup logging of suspicious activity Wstring paranoid_output_file; paranoid_output_file.set("hacks.log"); Global.config.getString("PARANOIDFILE", paranoid_output_file); if (paranoid_output_file != "STDOUT") { paranoid_output_device = new FileD(paranoid_output_file.get(), "a"); if (!paranoid_output_device) { cerr << "Could not open " << paranoid_output_file.get() << " for writing!" << endl; exit( -1); } } else { paranoid_output_device = new StdoutD; } MyMsgManager::setParanoidStream(paranoid_output_device); DBGMSG("Hack log started!"); PARANOIDMSG("Hack log started!"); Setup_Signals(); #ifdef _WINDOWS // ----- Initialize Winsock ----- WORD verReq = MAKEWORD(2, 2); WSADATA wsadata; int err = WSAStartup(verReq, &wsadata); if (err != 0) { ERRMSG("Winsock Init failed."); return 1; } if ((LOBYTE(wsadata.wVersion) != 2) || (HIBYTE(wsadata.wVersion) !=2)) { ERRMSG("Winsock DLL is not 2.2"); WSACleanup(); ERRMSG("Winsock Init failed."); return 1; } INFMSG("Winsock Init done."); #endif // Check game type & start matcher Wstring gametype = "unknown"; Global.config.getString("GAME", gametype); // Command-line override for gamemode. // This is for test suites, so they can use // the same config file as the corresponding // matchbot. const char *s = argv[0] + strlen(argv[0]); while (s > argv[0] && *s != '/') --s; if (*s == '/') ++s; Wstring exe = s; exe.toLower(); DBGMSG("Executable file is [" << exe.get() << "]"); if (gametype == "Generals") { DBGMSG("Generals matching behavior"); s_generalsMatcher = new GeneralsMatcher; s_generalsMatcher->connectAndLoop(); } else if (gametype == "GeneralsClient") { DBGMSG("Generals TEST client matching behavior"); s_generalsClientMatcher = new GeneralsClientMatcher; s_generalsClientMatcher->connectAndLoop(); } else { cerr << "\nNo valid GAME entry found!" << endl; exit( -1); } if (s_generalsMatcher) delete s_generalsMatcher; if (s_generalsClientMatcher) delete s_generalsClientMatcher; return 0; } /*----------------------------------------------------------------------+ | THREAD: logMonitor | | This thread is spawned once per execution. It will activate after | | midnight and create a new log file. The old one gets put into the | | logfiles directory. | `----------------------------------------------------------------------*/ void logMonitor(void *) { #ifdef _UNIX Xtime xtime; time_t curtime; //char timebuf[40]; char filenamebuf[128]; int delay = -1; Global.config.getInt("ROTATEDELAY", delay); DBGMSG("ROTATEDELAY: " << delay); if (delay == -1) return ; while (1) { curtime = time(NULL); // get the number of seconds that have passed since midnight // of the current day. curtime -= TimezoneOffset(); time_t timeofday = curtime % (delay); if ((timeofday > 0) && (timeofday <= 300)) { // We're within 5 minutes of midnight, switch the files. DBGMSG("about to switch."); Wstring logfilename = "matchbot.log"; Global.config.getString("OUTPUTFILE", logfilename); Wstring newfilename = "tmp.log"; Global.config.getString("ROTATEFILE", newfilename); MsgManager::ReplaceAllStreams((FileD*)output_device, logfilename.get(), newfilename.get()); Wstring logpath = "logs"; Global.config.getString("LOGPATH", logpath); xtime.update(); sprintf(filenamebuf, "%s/%02d%02d%04d_%02d%02d%02d_log", logpath.get(), xtime.getMonth(), xtime.getMDay(), xtime.getYear(), xtime.getHour(), xtime.getMinute(), xtime.getSecond()); rename(newfilename.get(), filenamebuf); DBGMSG("Normal: Just been switched. " << logfilename.get() << ", " << newfilename.get()); sleep(60*60*23); // sleep the next 23 hours } sleep(300); } #endif } void rotateOutput(void) { Xtime xtime; char filenamebuf[128]; Wstring logfilename = "matchbot.log"; Wstring newfilename = "tmp.log"; Wstring logpath = "logs"; DBGMSG("About to switch."); Global.config.getString("OUTPUTFILE", logfilename); Global.config.getString("ROTATEFILE", newfilename); Global.config.getString("LOGPATH", logpath); // This grabs the semaphore, renames the file, and switches the output device MsgManager::ReplaceAllStreams((FileD*)output_device, logfilename.get(), newfilename.get()); // clean up the tmp filename and move it to the log dir. sprintf(filenamebuf, "%s/%02d%02d%04d_%02d%02d%02d_log", logpath.get(), xtime.getMonth(), xtime.getMDay(), xtime.getYear(), xtime.getHour(), xtime.getMinute(), xtime.getSecond()); #ifdef _WINDOWS mkdir(logpath.get()); #else mkdir(logpath.get(), 00666); #endif rename(newfilename.get(), filenamebuf); DBGMSG("Normal: Just been switched. " << logfilename.get() << ", " << newfilename.get()); } void paranoidLogMonitor(void *) { #ifdef _UNIX Xtime xtime; time_t curtime; //char timebuf[40]; char filenamebuf[128]; int delay = -1; Global.config.getInt("ROTATEDELAY", delay); PARANOIDMSG("ROTATEDELAY: " << delay); if (delay == -1) return ; while (1) { curtime = time(NULL); // get the number of seconds that have passed since midnight // of the current day. curtime -= TimezoneOffset(); time_t timeofday = curtime % (delay); if ((timeofday > 0) && (timeofday <= 300)) { // We're within 5 minutes of midnight, switch the files. PARANOIDMSG("about to switch."); Wstring logfilename = "matchbot.log"; Global.config.getString("PARANOIDFILE", logfilename); Wstring newfilename = "tmp.log"; Global.config.getString("ROTATEPARANOIDFILE", newfilename); MyMsgManager::ReplaceAllStreams((FileD*)paranoid_output_device, logfilename.get(), newfilename.get()); Wstring logpath = "logs"; Global.config.getString("PARANOIDLOGPATH", logpath); xtime.update(); sprintf(filenamebuf, "%s/%02d%02d%04d_%02d%02d%02d_log", logpath.get(), xtime.getMonth(), xtime.getMDay(), xtime.getYear(), xtime.getHour(), xtime.getMinute(), xtime.getSecond()); rename(newfilename.get(), filenamebuf); PARANOIDMSG("Paranoid: Just been switched. " << logfilename.get() << ", " << newfilename.get()); sleep(60*60*23); // sleep the next 23 hours } sleep(300); } #endif } void rotateParanoid(void) { Xtime xtime; char filenamebuf[128]; Wstring logfilename = "matchbot.log"; Wstring newfilename = "tmp.log"; Wstring logpath = "logs"; PARANOIDMSG("About to switch."); Global.config.getString("PARANOIDFILE", logfilename); Global.config.getString("ROTATEPARANOIDFILE", newfilename); Global.config.getString("PARANOIDLOGPATH", logpath); // This grabs the semaphore, renames the file, and switches the output device MyMsgManager::ReplaceAllStreams((FileD*)output_device, logfilename.get(), newfilename.get()); // clean up the tmp filename and move it to the log dir. sprintf(filenamebuf, "%s/%02d%02d%04d_%02d%02d%02d_log", logpath.get(), xtime.getMonth(), xtime.getMDay(), xtime.getYear(), xtime.getHour(), xtime.getMinute(), xtime.getSecond()); #ifdef _WINDOWS mkdir(logpath.get()); #else mkdir(logpath.get(), 00666); #endif rename(newfilename.get(), filenamebuf); PARANOIDMSG("Paranoid: Just been switched. " << logfilename.get() << ", " << newfilename.get()); }