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 pFutureFileLogger->Set(log_logger_file(file: Logfile));
201 }
202 else
203 {
204 log_error("server", "failed to open '%s' for logging", g_Config.m_Logfile);
205 pFutureFileLogger->Set(log_logger_noop());
206 }
207 }
208 else
209 {
210 pFutureFileLogger->Set(log_logger_noop());
211 }
212
213 auto pServerLogger = std::make_shared<CServerLogger>(args&: pServer);
214 pEngine->SetAdditionalLogger(pServerLogger);
215
216 // run the server
217 log_trace("server", "initialization finished after %.2fms, starting...", (time_get() - MainStart) * 1000.0f / (float)time_freq());
218 int Ret = pServer->Run();
219
220 pServerLogger->OnServerDeletion();
221 // free
222 delete pKernel;
223
224 MysqlUninit();
225
226 return Ret;
227}
228
229#if defined(CONF_PLATFORM_ANDROID)
230#if !defined(ANDROID_PACKAGE_NAME_JNI)
231#error "ANDROID_PACKAGE_NAME_JNI must define the package name when compiling for Android (using underscores instead of dots, e.g. org_example_app)"
232#endif
233// Helpers to force macro expansion else the ANDROID_PACKAGE_NAME_JNI macro is not expanded
234#define EXPAND_MACRO(x) x
235#define JNI_MAKE_NAME(PACKAGE, CLASS, FUNCTION) Java_##PACKAGE##_##CLASS##_##FUNCTION
236#define JNI_EXPORTED_FUNCTION(PACKAGE, CLASS, FUNCTION, RETURN_TYPE, ...) \
237 extern "C" JNIEXPORT RETURN_TYPE JNICALL EXPAND_MACRO(JNI_MAKE_NAME(PACKAGE, CLASS, FUNCTION))(__VA_ARGS__)
238
239std::mutex AndroidNativeMutex;
240std::vector<std::string> vAndroidCommandQueue;
241
242std::vector<std::string> FetchAndroidServerCommandQueue()
243{
244 std::vector<std::string> vResult;
245 {
246 const std::unique_lock Lock(AndroidNativeMutex);
247 vResult.swap(vAndroidCommandQueue);
248 }
249 return vResult;
250}
251
252JNI_EXPORTED_FUNCTION(ANDROID_PACKAGE_NAME_JNI, NativeServer, runServer, jint, JNIEnv *pEnv, jobject Object, jstring WorkingDirectory, jobjectArray ArgumentsArray)
253{
254 // Set working directory to external storage location. This is not possible
255 // in Java so we pass the intended working directory to the native code.
256 const char *pWorkingDirectory = pEnv->GetStringUTFChars(WorkingDirectory, nullptr);
257 const bool WorkingDirectoryError = fs_chdir(pWorkingDirectory) != 0;
258 pEnv->ReleaseStringUTFChars(WorkingDirectory, pWorkingDirectory);
259 if(WorkingDirectoryError)
260 {
261 return -1001;
262 }
263
264 const jsize NumArguments = pEnv->GetArrayLength(ArgumentsArray);
265
266 std::vector<std::string> vArguments;
267 vArguments.reserve(NumArguments + 1);
268 vArguments.push_back(std::string(pWorkingDirectory) + "/" + GAME_NAME "-Server");
269 for(jsize ArgumentIndex = 0; ArgumentIndex < NumArguments; ArgumentIndex++)
270 {
271 jstring ArgumentString = (jstring)pEnv->GetObjectArrayElement(ArgumentsArray, ArgumentIndex);
272 const char *pArgumentString = pEnv->GetStringUTFChars(ArgumentString, nullptr);
273 vArguments.emplace_back(pArgumentString);
274 pEnv->ReleaseStringUTFChars(ArgumentString, pArgumentString);
275 }
276
277 std::vector<const char *> vpArguments;
278 vpArguments.reserve(vArguments.size());
279 for(const std::string &Argument : vArguments)
280 {
281 vpArguments.emplace_back(Argument.c_str());
282 }
283
284 return main(vpArguments.size(), vpArguments.data());
285}
286
287JNI_EXPORTED_FUNCTION(ANDROID_PACKAGE_NAME_JNI, NativeServer, executeCommand, void, JNIEnv *pEnv, jobject Object, jstring Command)
288{
289 const char *pCommand = pEnv->GetStringUTFChars(Command, nullptr);
290 {
291 const std::unique_lock Lock(AndroidNativeMutex);
292 vAndroidCommandQueue.emplace_back(pCommand);
293 }
294 pEnv->ReleaseStringUTFChars(Command, pCommand);
295}
296
297#undef EXPAND_MACRO
298#undef JNI_MAKE_NAME
299#undef JNI_EXPORTED_FUNCTION
300#endif
301