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