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 aBuf[IO_MAX_PATH_LENGTH];
132 char aBufName[IO_MAX_PATH_LENGTH];
133 char aDate[64];
134 str_timestamp(buffer: aDate, buffer_size: sizeof(aDate));
135 str_format(buffer: aBufName, buffer_size: sizeof(aBufName), format: "dumps/" GAME_NAME "-Server_%s_crash_log_%s_%d_%s.RTP", CONF_PLATFORM_STRING, aDate, process_id(), GIT_SHORTREV_HASH != nullptr ? GIT_SHORTREV_HASH : "");
136 pStorage->GetCompletePath(Type: IStorage::TYPE_SAVE, pDir: aBufName, pBuffer: aBuf, BufferSize: sizeof(aBuf));
137 crashdump_init_if_available(log_file_path: aBuf);
138 }
139
140 IConsole *pConsole = CreateConsole(FlagMask: CFGFLAG_SERVER | CFGFLAG_ECON).release();
141 pKernel->RegisterInterface(pInterface: pConsole);
142
143 IConfigManager *pConfigManager = CreateConfigManager();
144 pKernel->RegisterInterface(pInterface: pConfigManager);
145
146 IEngineAntibot *pEngineAntibot = CreateEngineAntibot();
147 pKernel->RegisterInterface(pInterface: pEngineAntibot); // IEngineAntibot
148 pKernel->RegisterInterface(pInterface: static_cast<IAntibot *>(pEngineAntibot), Destroy: false);
149
150 IGameServer *pGameServer = CreateGameServer();
151 pKernel->RegisterInterface(pInterface: pGameServer);
152
153 pEngine->Init();
154 pConsole->Init();
155 pConfigManager->Init();
156
157 // register all console commands
158 pServer->RegisterCommands();
159
160 // execute autoexec file
161 if(pStorage->FileExists(AUTOEXEC_SERVER_FILE, Type: IStorage::TYPE_ALL))
162 {
163 pConsole->ExecuteFile(AUTOEXEC_SERVER_FILE, ClientId: IConsole::CLIENT_ID_UNSPECIFIED);
164 }
165 else // fallback
166 {
167 pConsole->ExecuteFile(AUTOEXEC_FILE, ClientId: IConsole::CLIENT_ID_UNSPECIFIED);
168 }
169
170 // parse the command line arguments
171 if(argc > 1)
172 pConsole->ParseArguments(NumArgs: argc - 1, ppArguments: &argv[1]);
173
174 pConfigManager->SetReadOnly(pScriptName: "sv_max_clients", ReadOnly: true);
175 pConfigManager->SetReadOnly(pScriptName: "sv_test_cmds", ReadOnly: true);
176 pConfigManager->SetReadOnly(pScriptName: "sv_rescue", ReadOnly: true);
177 pConfigManager->SetReadOnly(pScriptName: "sv_port", ReadOnly: true);
178 pConfigManager->SetReadOnly(pScriptName: "bindaddr", ReadOnly: true);
179 pConfigManager->SetReadOnly(pScriptName: "logfile", ReadOnly: true);
180
181 if(g_Config.m_Logfile[0])
182 {
183 const int Mode = g_Config.m_Logappend ? IOFLAG_APPEND : IOFLAG_WRITE;
184 IOHANDLE Logfile = pStorage->OpenFile(pFilename: g_Config.m_Logfile, Flags: Mode, Type: IStorage::TYPE_SAVE_OR_ABSOLUTE);
185 if(Logfile)
186 {
187 pFutureFileLogger->Set(log_logger_file(file: Logfile));
188 }
189 else
190 {
191 log_error("server", "failed to open '%s' for logging", g_Config.m_Logfile);
192 pFutureFileLogger->Set(log_logger_noop());
193 }
194 }
195 else
196 {
197 pFutureFileLogger->Set(log_logger_noop());
198 }
199
200 auto pServerLogger = std::make_shared<CServerLogger>(args&: pServer);
201 pEngine->SetAdditionalLogger(pServerLogger);
202
203 // run the server
204 log_trace("server", "initialization finished after %.2fms, starting...", (time_get() - MainStart) * 1000.0f / (float)time_freq());
205 int Ret = pServer->Run();
206
207 pServerLogger->OnServerDeletion();
208 // free
209 delete pKernel;
210
211 MysqlUninit();
212
213 return Ret;
214}
215
216#if defined(CONF_PLATFORM_ANDROID)
217#if !defined(ANDROID_PACKAGE_NAME_JNI)
218#error "ANDROID_PACKAGE_NAME_JNI must define the package name when compiling for Android (using underscores instead of dots, e.g. org_example_app)"
219#endif
220// Helpers to force macro expansion else the ANDROID_PACKAGE_NAME_JNI macro is not expanded
221#define EXPAND_MACRO(x) x
222#define JNI_MAKE_NAME(PACKAGE, CLASS, FUNCTION) Java_##PACKAGE##_##CLASS##_##FUNCTION
223#define JNI_EXPORTED_FUNCTION(PACKAGE, CLASS, FUNCTION, RETURN_TYPE, ...) \
224 extern "C" JNIEXPORT RETURN_TYPE JNICALL EXPAND_MACRO(JNI_MAKE_NAME(PACKAGE, CLASS, FUNCTION))(__VA_ARGS__)
225
226std::mutex AndroidNativeMutex;
227std::vector<std::string> vAndroidCommandQueue;
228
229std::vector<std::string> FetchAndroidServerCommandQueue()
230{
231 std::vector<std::string> vResult;
232 {
233 const std::unique_lock Lock(AndroidNativeMutex);
234 vResult.swap(vAndroidCommandQueue);
235 }
236 return vResult;
237}
238
239JNI_EXPORTED_FUNCTION(ANDROID_PACKAGE_NAME_JNI, NativeServer, runServer, jint, JNIEnv *pEnv, jobject Object, jstring WorkingDirectory, jobjectArray ArgumentsArray)
240{
241 // Set working directory to external storage location. This is not possible
242 // in Java so we pass the intended working directory to the native code.
243 const char *pWorkingDirectory = pEnv->GetStringUTFChars(WorkingDirectory, nullptr);
244 const bool WorkingDirectoryError = fs_chdir(pWorkingDirectory) != 0;
245 pEnv->ReleaseStringUTFChars(WorkingDirectory, pWorkingDirectory);
246 if(WorkingDirectoryError)
247 {
248 return -1001;
249 }
250
251 const jsize NumArguments = pEnv->GetArrayLength(ArgumentsArray);
252
253 std::vector<std::string> vArguments;
254 vArguments.reserve(NumArguments + 1);
255 vArguments.push_back(std::string(pWorkingDirectory) + "/" + GAME_NAME "-Server");
256 for(jsize ArgumentIndex = 0; ArgumentIndex < NumArguments; ArgumentIndex++)
257 {
258 jstring ArgumentString = (jstring)pEnv->GetObjectArrayElement(ArgumentsArray, ArgumentIndex);
259 const char *pArgumentString = pEnv->GetStringUTFChars(ArgumentString, nullptr);
260 vArguments.emplace_back(pArgumentString);
261 pEnv->ReleaseStringUTFChars(ArgumentString, pArgumentString);
262 }
263
264 std::vector<const char *> vpArguments;
265 vpArguments.reserve(vArguments.size());
266 for(const std::string &Argument : vArguments)
267 {
268 vpArguments.emplace_back(Argument.c_str());
269 }
270
271 return main(vpArguments.size(), vpArguments.data());
272}
273
274JNI_EXPORTED_FUNCTION(ANDROID_PACKAGE_NAME_JNI, NativeServer, executeCommand, void, JNIEnv *pEnv, jobject Object, jstring Command)
275{
276 const char *pCommand = pEnv->GetStringUTFChars(Command, nullptr);
277 {
278 const std::unique_lock Lock(AndroidNativeMutex);
279 vAndroidCommandQueue.emplace_back(pCommand);
280 }
281 pEnv->ReleaseStringUTFChars(Command, pCommand);
282}
283
284#undef EXPAND_MACRO
285#undef JNI_MAKE_NAME
286#undef JNI_EXPORTED_FUNCTION
287#endif
288