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