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