1#include <base/logger.h>
2#include <base/system.h>
3#include <base/windows.h>
4
5#include <engine/console.h>
6#include <engine/engine.h>
7#include <engine/map.h>
8#include <engine/server.h>
9#include <engine/server/antibot.h>
10#include <engine/server/databases/connection.h>
11#include <engine/server/server.h>
12#include <engine/server/server_logger.h>
13#include <engine/shared/assertion_logger.h>
14#include <engine/shared/config.h>
15#include <engine/storage.h>
16
17#include <game/version.h>
18
19#include <mutex>
20#include <vector>
21
22#if defined(CONF_FAMILY_WINDOWS)
23#include <windows.h>
24#elif defined(CONF_PLATFORM_ANDROID)
25#include <jni.h>
26#endif
27
28#include <csignal>
29
30static volatile sig_atomic_t InterruptSignaled = 0;
31
32bool IsInterrupted()
33{
34 return InterruptSignaled;
35}
36
37static void HandleSigIntTerm(int Param)
38{
39 InterruptSignaled = 1;
40
41 // Exit the next time a signal is received
42 signal(SIGINT, SIG_DFL);
43 signal(SIGTERM, SIG_DFL);
44}
45
46int main(int argc, const char **argv)
47{
48 const int64_t MainStart = time_get();
49
50 CCmdlineFix CmdlineFix(&argc, &argv);
51
52#if !defined(CONF_PLATFORM_ANDROID)
53 bool Silent = false;
54
55 for(int i = 1; i < argc; i++)
56 {
57 if(str_comp(a: "-s", b: argv[i]) == 0 || str_comp(a: "--silent", b: argv[i]) == 0)
58 {
59 Silent = true;
60#if defined(CONF_FAMILY_WINDOWS)
61 ShowWindow(GetConsoleWindow(), SW_HIDE);
62#endif
63 break;
64 }
65 }
66#endif
67
68#if defined(CONF_FAMILY_WINDOWS)
69 CWindowsComLifecycle WindowsComLifecycle(false);
70#endif
71
72 std::vector<std::shared_ptr<ILogger>> vpLoggers;
73 std::shared_ptr<ILogger> pStdoutLogger;
74#if defined(CONF_PLATFORM_ANDROID)
75 pStdoutLogger = std::shared_ptr<ILogger>(log_logger_android());
76#else
77 if(!Silent)
78 {
79 pStdoutLogger = std::shared_ptr<ILogger>(log_logger_stdout());
80 }
81#endif
82 if(pStdoutLogger)
83 {
84 vpLoggers.push_back(x: pStdoutLogger);
85 }
86 std::shared_ptr<CFutureLogger> pFutureFileLogger = std::make_shared<CFutureLogger>();
87 vpLoggers.push_back(x: pFutureFileLogger);
88 std::shared_ptr<CFutureLogger> pFutureConsoleLogger = std::make_shared<CFutureLogger>();
89 vpLoggers.push_back(x: pFutureConsoleLogger);
90 std::shared_ptr<CFutureLogger> pFutureAssertionLogger = std::make_shared<CFutureLogger>();
91 vpLoggers.push_back(x: pFutureAssertionLogger);
92 log_set_global_logger(logger: log_logger_collection(vpLoggers: std::move(vpLoggers)).release());
93
94 if(MysqlInit() != 0)
95 {
96 log_error("mysql", "failed to initialize MySQL library");
97 return -1;
98 }
99
100 signal(SIGINT, handler: HandleSigIntTerm);
101 signal(SIGTERM, handler: HandleSigIntTerm);
102
103 CServer *pServer = CreateServer();
104 pServer->SetLoggers(pFileLogger: pFutureFileLogger, pStdoutLogger: std::move(pStdoutLogger));
105
106 IKernel *pKernel = IKernel::Create();
107 pKernel->RegisterInterface(pInterface: pServer);
108
109 // create the components
110 IEngine *pEngine = CreateEngine(GAME_NAME, pFutureLogger: pFutureConsoleLogger);
111 pKernel->RegisterInterface(pInterface: pEngine);
112
113 IStorage *pStorage = CreateStorage(InitializationType: IStorage::EInitializationType::SERVER, NumArgs: argc, ppArguments: argv);
114 if(!pStorage)
115 {
116 log_error("server", "failed to initialize storage");
117 return -1;
118 }
119 pKernel->RegisterInterface(pInterface: pStorage);
120
121 pFutureAssertionLogger->Set(CreateAssertionLogger(pStorage, GAME_NAME));
122
123 {
124 char aBuf[IO_MAX_PATH_LENGTH];
125 char aBufName[IO_MAX_PATH_LENGTH];
126 char aDate[64];
127 str_timestamp(buffer: aDate, buffer_size: sizeof(aDate));
128 str_format(buffer: aBufName, buffer_size: sizeof(aBufName), format: "dumps/" GAME_NAME "-Server_%s_crash_log_%s_%d_%s.RTP", CONF_PLATFORM_STRING, aDate, pid(), GIT_SHORTREV_HASH != nullptr ? GIT_SHORTREV_HASH : "");
129 pStorage->GetCompletePath(Type: IStorage::TYPE_SAVE, pDir: aBufName, pBuffer: aBuf, BufferSize: sizeof(aBuf));
130 crashdump_init_if_available(log_file_path: aBuf);
131 }
132
133 IConsole *pConsole = CreateConsole(FlagMask: CFGFLAG_SERVER | CFGFLAG_ECON).release();
134 pKernel->RegisterInterface(pInterface: pConsole);
135
136 IConfigManager *pConfigManager = CreateConfigManager();
137 pKernel->RegisterInterface(pInterface: pConfigManager);
138
139 IEngineMap *pEngineMap = CreateEngineMap();
140 pKernel->RegisterInterface(pInterface: pEngineMap); // IEngineMap
141 pKernel->RegisterInterface(pInterface: static_cast<IMap *>(pEngineMap), Destroy: false);
142
143 IEngineAntibot *pEngineAntibot = CreateEngineAntibot();
144 pKernel->RegisterInterface(pInterface: pEngineAntibot); // IEngineAntibot
145 pKernel->RegisterInterface(pInterface: static_cast<IAntibot *>(pEngineAntibot), Destroy: false);
146
147 IGameServer *pGameServer = CreateGameServer();
148 pKernel->RegisterInterface(pInterface: pGameServer);
149
150 pEngine->Init();
151 pConsole->Init();
152 pConfigManager->Init();
153
154 // register all console commands
155 pServer->RegisterCommands();
156
157 // execute autoexec file
158 if(pStorage->FileExists(AUTOEXEC_SERVER_FILE, Type: IStorage::TYPE_ALL))
159 {
160 pConsole->ExecuteFile(AUTOEXEC_SERVER_FILE);
161 }
162 else // fallback
163 {
164 pConsole->ExecuteFile(AUTOEXEC_FILE);
165 }
166
167 // parse the command line arguments
168 if(argc > 1)
169 pConsole->ParseArguments(NumArgs: argc - 1, ppArguments: &argv[1]);
170
171 pConfigManager->SetReadOnly(pScriptName: "sv_max_clients", ReadOnly: true);
172 pConfigManager->SetReadOnly(pScriptName: "sv_test_cmds", ReadOnly: true);
173 pConfigManager->SetReadOnly(pScriptName: "sv_rescue", ReadOnly: true);
174 pConfigManager->SetReadOnly(pScriptName: "sv_port", ReadOnly: true);
175 pConfigManager->SetReadOnly(pScriptName: "bindaddr", ReadOnly: true);
176 pConfigManager->SetReadOnly(pScriptName: "logfile", ReadOnly: true);
177
178 if(g_Config.m_Logfile[0])
179 {
180 const int Mode = g_Config.m_Logappend ? IOFLAG_APPEND : IOFLAG_WRITE;
181 IOHANDLE Logfile = pStorage->OpenFile(pFilename: g_Config.m_Logfile, Flags: Mode, Type: IStorage::TYPE_SAVE_OR_ABSOLUTE);
182 if(Logfile)
183 {
184 pFutureFileLogger->Set(log_logger_file(file: Logfile));
185 }
186 else
187 {
188 log_error("server", "failed to open '%s' for logging", g_Config.m_Logfile);
189 pFutureFileLogger->Set(log_logger_noop());
190 }
191 }
192 else
193 {
194 pFutureFileLogger->Set(log_logger_noop());
195 }
196
197 auto pServerLogger = std::make_shared<CServerLogger>(args&: pServer);
198 pEngine->SetAdditionalLogger(pServerLogger);
199
200 // run the server
201 log_trace("server", "initialization finished after %.2fms, starting...", (time_get() - MainStart) * 1000.0f / (float)time_freq());
202 int Ret = pServer->Run();
203
204 pServerLogger->OnServerDeletion();
205 // free
206 delete pKernel;
207
208 MysqlUninit();
209
210 return Ret;
211}
212
213#if defined(CONF_PLATFORM_ANDROID)
214#if !defined(ANDROID_PACKAGE_NAME)
215#error "ANDROID_PACKAGE_NAME must define the package name when compiling for Android (using underscores instead of dots, e.g. org_example_app)"
216#endif
217// Helpers to force macro expansion else the ANDROID_PACKAGE_NAME macro is not expanded
218#define EXPAND_MACRO(x) x
219#define JNI_MAKE_NAME(PACKAGE, CLASS, FUNCTION) Java_##PACKAGE##_##CLASS##_##FUNCTION
220#define JNI_EXPORTED_FUNCTION(PACKAGE, CLASS, FUNCTION, RETURN_TYPE, ...) \
221 extern "C" JNIEXPORT RETURN_TYPE JNICALL EXPAND_MACRO(JNI_MAKE_NAME(PACKAGE, CLASS, FUNCTION))(__VA_ARGS__)
222
223std::mutex AndroidNativeMutex;
224std::vector<std::string> vAndroidCommandQueue;
225
226std::vector<std::string> FetchAndroidServerCommandQueue()
227{
228 std::vector<std::string> vResult;
229 {
230 const std::unique_lock Lock(AndroidNativeMutex);
231 vResult.swap(vAndroidCommandQueue);
232 }
233 return vResult;
234}
235
236JNI_EXPORTED_FUNCTION(ANDROID_PACKAGE_NAME, NativeServer, runServer, jint, JNIEnv *pEnv, jobject Object, jstring WorkingDirectory, jobjectArray ArgumentsArray)
237{
238 // Set working directory to external storage location. This is not possible
239 // in Java so we pass the intended working directory to the native code.
240 const char *pWorkingDirectory = pEnv->GetStringUTFChars(WorkingDirectory, nullptr);
241 const bool WorkingDirectoryError = fs_chdir(pWorkingDirectory) != 0;
242 pEnv->ReleaseStringUTFChars(WorkingDirectory, pWorkingDirectory);
243 if(WorkingDirectoryError)
244 {
245 return -1001;
246 }
247
248 const jsize NumArguments = pEnv->GetArrayLength(ArgumentsArray);
249
250 std::vector<std::string> vArguments;
251 vArguments.reserve(NumArguments + 1);
252 vArguments.push_back(std::string(pWorkingDirectory) + "/" + GAME_NAME "-Server");
253 for(jsize ArgumentIndex = 0; ArgumentIndex < NumArguments; ArgumentIndex++)
254 {
255 jstring ArgumentString = (jstring)pEnv->GetObjectArrayElement(ArgumentsArray, ArgumentIndex);
256 const char *pArgumentString = pEnv->GetStringUTFChars(ArgumentString, nullptr);
257 vArguments.emplace_back(pArgumentString);
258 pEnv->ReleaseStringUTFChars(ArgumentString, pArgumentString);
259 }
260
261 std::vector<const char *> vpArguments;
262 vpArguments.reserve(vArguments.size());
263 for(const std::string &Argument : vArguments)
264 {
265 vpArguments.emplace_back(Argument.c_str());
266 }
267
268 return main(vpArguments.size(), vpArguments.data());
269}
270
271JNI_EXPORTED_FUNCTION(ANDROID_PACKAGE_NAME, NativeServer, executeCommand, void, JNIEnv *pEnv, jobject Object, jstring Command)
272{
273 const char *pCommand = pEnv->GetStringUTFChars(Command, nullptr);
274 {
275 const std::unique_lock Lock(AndroidNativeMutex);
276 vAndroidCommandQueue.emplace_back(pCommand);
277 }
278 pEnv->ReleaseStringUTFChars(Command, pCommand);
279}
280
281#undef EXPAND_MACRO
282#undef JNI_MAKE_NAME
283#undef JNI_EXPORTED_FUNCTION
284#endif
285