main.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409
  1. /*
  2. ** Command & Conquer Generals Zero Hour(tm)
  3. ** Copyright 2025 Electronic Arts Inc.
  4. **
  5. ** This program is free software: you can redistribute it and/or modify
  6. ** it under the terms of the GNU General Public License as published by
  7. ** the Free Software Foundation, either version 3 of the License, or
  8. ** (at your option) any later version.
  9. **
  10. ** This program is distributed in the hope that it will be useful,
  11. ** but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. ** GNU General Public License for more details.
  14. **
  15. ** You should have received a copy of the GNU General Public License
  16. ** along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. #ifdef _WIN32
  19. #include <process.h>
  20. #endif
  21. #include <cstdlib>
  22. #include <csignal>
  23. #include <iostream>
  24. #ifdef _WIN32
  25. #include <direct.h>
  26. #else
  27. #include <sys/types.h>
  28. #include <sys/stat.h>
  29. #endif
  30. //#define THREADSAFE_HEADER
  31. #include <wstring.h>
  32. #include <wdebug.h>
  33. #include <filed.h>
  34. #include <stdoutd.h>
  35. #include <wstypes.h>
  36. #include <xtime.h>
  37. #include "global.h"
  38. #include "generals.h"
  39. #include "timezone.h"
  40. #include <threadfac.h>
  41. #include <tcp.h>
  42. #include "mydebug.h"
  43. #ifdef _UNIX
  44. using namespace std;
  45. #else
  46. #define sleep(x) Sleep(1000 * (x))
  47. #endif
  48. static char *Program_Usage = "A config filename can be given on the command line (default=matchbot.cfg)\n";
  49. void logMonitor(void *);
  50. void paranoidLogMonitor(void *);
  51. OutputDevice * output_device = NULL;
  52. OutputDevice * paranoid_output_device = NULL;
  53. void Signal_Quit(int)
  54. {
  55. INFMSG("Exiting due to signal.");
  56. exit(2);
  57. }
  58. void Setup_Signals(void)
  59. {
  60. #ifdef _UNIX
  61. struct sigaction act, oact;
  62. act.sa_handler = Signal_Quit;
  63. sigemptyset(&act.sa_mask);
  64. act.sa_flags = 0;
  65. sigaction(SIGTERM, &act, &oact);
  66. sigaction(SIGINT, &act, &oact);
  67. #endif
  68. }
  69. int VerifyFileDescriptors(int requested)
  70. {
  71. #ifdef _UNIX
  72. struct rlimit limit;
  73. if (!getrlimit(_SC_OPEN_MAX, &limit))
  74. {
  75. INFMSG("Hard limit on file descriptors: " << limit.rlim_max);
  76. if (limit.rlim_max < (unsigned int)requested)
  77. {
  78. ERRMSG("Too many file descriptors requested");
  79. ERRMSG("Hard Limit: " << limit.rlim_max << ", requested: " << requested);
  80. requested = limit.rlim_max;
  81. }
  82. limit.rlim_cur = limit.rlim_max; /* make soft limit the max */
  83. if (setrlimit(_SC_OPEN_MAX, &limit) == -1)
  84. {
  85. ERRMSG("Error setting max file descriptors to " << limit.rlim_cur);
  86. exit(-1);
  87. }
  88. INFMSG("Soft limit on file descriptors: " << limit.rlim_cur);
  89. }
  90. else
  91. {
  92. ERRMSG("Couldn't get limit for _SC_OPEN_MAX");
  93. exit(-1);
  94. }
  95. #endif
  96. return requested;
  97. }
  98. GeneralsMatcher *s_generalsMatcher = NULL;
  99. GeneralsClientMatcher *s_generalsClientMatcher = NULL;
  100. int main(int argc, char ** argv)
  101. {
  102. Wstring config_fname = "matchbot.cfg";
  103. // You can specify the config file on the command line
  104. if (argc == 2)
  105. config_fname = argv[1];
  106. // Read the config file
  107. FILE *fp;
  108. if ((fp = fopen(config_fname.get(), "r")) == NULL)
  109. {
  110. cerr << "\nCan't open the config file '" << config_fname.get() << "'\n\n";
  111. cerr << Program_Usage << endl;
  112. exit( -1);
  113. }
  114. fclose(fp);
  115. Global.ReadFile(config_fname.get());
  116. // Setup debugging & logging output
  117. Wstring output_file;
  118. output_file.set("matchbot.log");
  119. Global.config.getString("OUTPUTFILE", output_file);
  120. if (output_file != "STDOUT")
  121. {
  122. int append = 1;
  123. Global.config.getInt("APPENDTOLOG", append);
  124. if (append)
  125. output_device = new FileD(output_file.get(), "a");
  126. else
  127. output_device = new FileD(output_file.get(), "w");
  128. if (!output_device)
  129. {
  130. cerr << "Could not open " << output_file.get() << " for writing!" << endl;
  131. exit( -1);
  132. }
  133. }
  134. else
  135. {
  136. output_device = new StdoutD;
  137. }
  138. MsgManager::setAllStreams(output_device);
  139. DBGMSG("Matching bot started!");
  140. INFMSG("Matching bot " << argv[0] << " started!");
  141. // Setup logging of suspicious activity
  142. Wstring paranoid_output_file;
  143. paranoid_output_file.set("hacks.log");
  144. Global.config.getString("PARANOIDFILE", paranoid_output_file);
  145. if (paranoid_output_file != "STDOUT")
  146. {
  147. paranoid_output_device = new FileD(paranoid_output_file.get(), "a");
  148. if (!paranoid_output_device)
  149. {
  150. cerr << "Could not open " << paranoid_output_file.get() << " for writing!" << endl;
  151. exit( -1);
  152. }
  153. }
  154. else
  155. {
  156. paranoid_output_device = new StdoutD;
  157. }
  158. MyMsgManager::setParanoidStream(paranoid_output_device);
  159. DBGMSG("Hack log started!");
  160. PARANOIDMSG("Hack log started!");
  161. Setup_Signals();
  162. #ifdef _WINDOWS
  163. // ----- Initialize Winsock -----
  164. WORD verReq = MAKEWORD(2, 2);
  165. WSADATA wsadata;
  166. int err = WSAStartup(verReq, &wsadata);
  167. if (err != 0)
  168. {
  169. ERRMSG("Winsock Init failed.");
  170. return 1;
  171. }
  172. if ((LOBYTE(wsadata.wVersion) != 2) || (HIBYTE(wsadata.wVersion) !=2))
  173. {
  174. ERRMSG("Winsock DLL is not 2.2");
  175. WSACleanup();
  176. ERRMSG("Winsock Init failed.");
  177. return 1;
  178. }
  179. INFMSG("Winsock Init done.");
  180. #endif
  181. // Check game type & start matcher
  182. Wstring gametype = "unknown";
  183. Global.config.getString("GAME", gametype);
  184. // Command-line override for gamemode.
  185. // This is for test suites, so they can use
  186. // the same config file as the corresponding
  187. // matchbot.
  188. const char *s = argv[0] + strlen(argv[0]);
  189. while (s > argv[0] && *s != '/')
  190. --s;
  191. if (*s == '/')
  192. ++s;
  193. Wstring exe = s;
  194. exe.toLower();
  195. DBGMSG("Executable file is [" << exe.get() << "]");
  196. if (gametype == "Generals")
  197. {
  198. DBGMSG("Generals matching behavior");
  199. s_generalsMatcher = new GeneralsMatcher;
  200. s_generalsMatcher->connectAndLoop();
  201. }
  202. else if (gametype == "GeneralsClient")
  203. {
  204. DBGMSG("Generals TEST client matching behavior");
  205. s_generalsClientMatcher = new GeneralsClientMatcher;
  206. s_generalsClientMatcher->connectAndLoop();
  207. }
  208. else
  209. {
  210. cerr << "\nNo valid GAME entry found!" << endl;
  211. exit( -1);
  212. }
  213. if (s_generalsMatcher)
  214. delete s_generalsMatcher;
  215. if (s_generalsClientMatcher)
  216. delete s_generalsClientMatcher;
  217. return 0;
  218. }
  219. /*----------------------------------------------------------------------+
  220. | THREAD: logMonitor |
  221. | This thread is spawned once per execution. It will activate after |
  222. | midnight and create a new log file. The old one gets put into the |
  223. | logfiles directory. |
  224. `----------------------------------------------------------------------*/
  225. void logMonitor(void *)
  226. {
  227. #ifdef _UNIX
  228. Xtime xtime;
  229. time_t curtime;
  230. //char timebuf[40];
  231. char filenamebuf[128];
  232. int delay = -1;
  233. Global.config.getInt("ROTATEDELAY", delay);
  234. DBGMSG("ROTATEDELAY: " << delay);
  235. if (delay == -1)
  236. return ;
  237. while (1)
  238. {
  239. curtime = time(NULL);
  240. // get the number of seconds that have passed since midnight
  241. // of the current day.
  242. curtime -= TimezoneOffset();
  243. time_t timeofday = curtime % (delay);
  244. if ((timeofday > 0) && (timeofday <= 300))
  245. {
  246. // We're within 5 minutes of midnight, switch the files.
  247. DBGMSG("about to switch.");
  248. Wstring logfilename = "matchbot.log";
  249. Global.config.getString("OUTPUTFILE", logfilename);
  250. Wstring newfilename = "tmp.log";
  251. Global.config.getString("ROTATEFILE", newfilename);
  252. MsgManager::ReplaceAllStreams((FileD*)output_device, logfilename.get(), newfilename.get());
  253. Wstring logpath = "logs";
  254. Global.config.getString("LOGPATH", logpath);
  255. xtime.update();
  256. sprintf(filenamebuf, "%s/%02d%02d%04d_%02d%02d%02d_log", logpath.get(), xtime.getMonth(),
  257. xtime.getMDay(), xtime.getYear(), xtime.getHour(), xtime.getMinute(), xtime.getSecond());
  258. rename(newfilename.get(), filenamebuf);
  259. DBGMSG("Normal: Just been switched. " << logfilename.get() << ", " << newfilename.get());
  260. sleep(60*60*23); // sleep the next 23 hours
  261. }
  262. sleep(300);
  263. }
  264. #endif
  265. }
  266. void rotateOutput(void)
  267. {
  268. Xtime xtime;
  269. char filenamebuf[128];
  270. Wstring logfilename = "matchbot.log";
  271. Wstring newfilename = "tmp.log";
  272. Wstring logpath = "logs";
  273. DBGMSG("About to switch.");
  274. Global.config.getString("OUTPUTFILE", logfilename);
  275. Global.config.getString("ROTATEFILE", newfilename);
  276. Global.config.getString("LOGPATH", logpath);
  277. // This grabs the semaphore, renames the file, and switches the output device
  278. MsgManager::ReplaceAllStreams((FileD*)output_device, logfilename.get(),
  279. newfilename.get());
  280. // clean up the tmp filename and move it to the log dir.
  281. sprintf(filenamebuf, "%s/%02d%02d%04d_%02d%02d%02d_log", logpath.get(),
  282. xtime.getMonth(), xtime.getMDay(), xtime.getYear(), xtime.getHour(),
  283. xtime.getMinute(), xtime.getSecond());
  284. #ifdef _WINDOWS
  285. mkdir(logpath.get());
  286. #else
  287. mkdir(logpath.get(), 00666);
  288. #endif
  289. rename(newfilename.get(), filenamebuf);
  290. DBGMSG("Normal: Just been switched. " << logfilename.get() << ", " <<
  291. newfilename.get());
  292. }
  293. void paranoidLogMonitor(void *)
  294. {
  295. #ifdef _UNIX
  296. Xtime xtime;
  297. time_t curtime;
  298. //char timebuf[40];
  299. char filenamebuf[128];
  300. int delay = -1;
  301. Global.config.getInt("ROTATEDELAY", delay);
  302. PARANOIDMSG("ROTATEDELAY: " << delay);
  303. if (delay == -1)
  304. return ;
  305. while (1)
  306. {
  307. curtime = time(NULL);
  308. // get the number of seconds that have passed since midnight
  309. // of the current day.
  310. curtime -= TimezoneOffset();
  311. time_t timeofday = curtime % (delay);
  312. if ((timeofday > 0) && (timeofday <= 300))
  313. {
  314. // We're within 5 minutes of midnight, switch the files.
  315. PARANOIDMSG("about to switch.");
  316. Wstring logfilename = "matchbot.log";
  317. Global.config.getString("PARANOIDFILE", logfilename);
  318. Wstring newfilename = "tmp.log";
  319. Global.config.getString("ROTATEPARANOIDFILE", newfilename);
  320. MyMsgManager::ReplaceAllStreams((FileD*)paranoid_output_device, logfilename.get(), newfilename.get());
  321. Wstring logpath = "logs";
  322. Global.config.getString("PARANOIDLOGPATH", logpath);
  323. xtime.update();
  324. sprintf(filenamebuf, "%s/%02d%02d%04d_%02d%02d%02d_log", logpath.get(), xtime.getMonth(),
  325. xtime.getMDay(), xtime.getYear(), xtime.getHour(), xtime.getMinute(), xtime.getSecond());
  326. rename(newfilename.get(), filenamebuf);
  327. PARANOIDMSG("Paranoid: Just been switched. " << logfilename.get() << ", " << newfilename.get());
  328. sleep(60*60*23); // sleep the next 23 hours
  329. }
  330. sleep(300);
  331. }
  332. #endif
  333. }
  334. void rotateParanoid(void)
  335. {
  336. Xtime xtime;
  337. char filenamebuf[128];
  338. Wstring logfilename = "matchbot.log";
  339. Wstring newfilename = "tmp.log";
  340. Wstring logpath = "logs";
  341. PARANOIDMSG("About to switch.");
  342. Global.config.getString("PARANOIDFILE", logfilename);
  343. Global.config.getString("ROTATEPARANOIDFILE", newfilename);
  344. Global.config.getString("PARANOIDLOGPATH", logpath);
  345. // This grabs the semaphore, renames the file, and switches the output device
  346. MyMsgManager::ReplaceAllStreams((FileD*)output_device, logfilename.get(),
  347. newfilename.get());
  348. // clean up the tmp filename and move it to the log dir.
  349. sprintf(filenamebuf, "%s/%02d%02d%04d_%02d%02d%02d_log", logpath.get(),
  350. xtime.getMonth(), xtime.getMDay(), xtime.getYear(), xtime.getHour(),
  351. xtime.getMinute(), xtime.getSecond());
  352. #ifdef _WINDOWS
  353. mkdir(logpath.get());
  354. #else
  355. mkdir(logpath.get(), 00666);
  356. #endif
  357. rename(newfilename.get(), filenamebuf);
  358. PARANOIDMSG("Paranoid: Just been switched. " << logfilename.get() << ", " <<
  359. newfilename.get());
  360. }