1/*
2 * LegacyClonk
3 *
4 * Copyright (c) 1998-2000, Matthes Bender (RedWolf Design)
5 * Copyright (c) 2017-2021, The LegacyClonk Team and contributors
6 *
7 * Distributed under the terms of the ISC license; see accompanying file
8 * "COPYING" for details.
9 *
10 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11 * See accompanying file "TRADEMARK" for details.
12 *
13 * To redistribute this file separately, substitute the full license texts
14 * for the above references.
15 */
16
17/* Main program entry point */
18
19#include <C4Include.h>
20#include <C4Application.h>
21
22#include <C4Console.h>
23#include <C4FullScreen.h>
24#include <C4Log.h>
25
26#ifdef WITH_DEVELOPER_MODE
27#include <gtk/gtk.h>
28#endif
29
30// debug memory management
31#if !defined(NODEBUGMEM) && defined(_MSC_VER)
32#include <crtdbg.h>
33#endif
34
35#ifdef _WIN32
36#include "C4Com.h"
37#include "C4WinRT.h"
38
39#include <span>
40#include <string_view>
41
42#include <objbase.h>
43#include <tchar.h>
44#endif
45
46#ifdef __APPLE__
47#include "MacAppTranslocation.h"
48#include <libgen.h>
49#endif
50
51C4Application Application;
52C4Console Console;
53C4FullScreen FullScreen;
54C4Game Game;
55C4Config Config;
56
57#ifdef _WIN32
58
59void InstallCrashHandler();
60
61int ClonkMain(const HINSTANCE instance, const int cmdShow, const int argc, char **const argv, const LPSTR commandLine)
62{
63#if defined(_MSC_VER)
64 // enable debugheap!
65 _CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
66#endif
67
68 SetCurrentProcessExplicitAppUserModelID(_CRT_WIDE(STD_APPUSERMODELID));
69
70 InstallCrashHandler();
71
72#ifndef USE_CONSOLE
73#ifdef NDEBUG
74 const std::span args{argv, static_cast<std::size_t>(argc)};
75
76 const auto hasArgument = [&args](const std::string_view argument)
77 {
78 return std::ranges::find(args, argument) != std::ranges::end(args);
79 };
80
81 if (hasArgument("/allocconsole"))
82#endif
83 {
84 if (!AllocConsole())
85 {
86 return C4XRV_Failure;
87 }
88
89 freopen("CONIN$", "r", stdin);
90 freopen("CONOUT$", "w", stdout);
91 freopen("CONOUT$", "w", stderr);
92 }
93#endif
94
95 C4Com com;
96
97 try
98 {
99 com = C4Com{winrt::apartment_type::multi_threaded};
100 }
101 catch (const winrt::hresult_error &e)
102 {
103 MessageBoxW(nullptr, (std::wstring{L"Failed to initialize COM: "} + e.message()).c_str(), _CRT_WIDE(STD_PRODUCT), MB_ICONERROR);
104 return C4XRV_Failure;
105 }
106
107 // Init application
108 try
109 {
110 Application.Init(instance, cmdShow, commandLine);
111 }
112 catch (const CStdApp::StartupException &e)
113 {
114 Application.Clear();
115 MessageBoxA(nullptr, e.what(), STD_PRODUCT, MB_ICONERROR);
116 return C4XRV_Failure;
117 }
118
119 // Run it
120 Application.Run();
121 Application.Clear();
122
123 // Return exit code
124 return C4XRV_Completed;
125}
126
127int WINAPI WinMain(HINSTANCE hInst,
128 HINSTANCE hPrevInstance,
129 LPSTR lpszCmdParam,
130 int nCmdShow)
131{
132 return ClonkMain(hInst, nCmdShow, __argc, __argv, lpszCmdParam);
133}
134
135int main(const int argc, char **const argv)
136{
137 // Get command line, go over program name
138 char *commandLine{GetCommandLineA()};
139 if (*commandLine == L'"')
140 {
141 ++commandLine;
142 while (*commandLine && *commandLine != '"')
143 {
144 ++commandLine;
145 }
146
147 if (*commandLine == '"')
148 {
149 ++commandLine;
150 }
151 }
152 else
153 {
154 while (*commandLine && *commandLine != ' ')
155 {
156 ++commandLine;
157 }
158 }
159 while (*commandLine == ' ')
160 {
161 ++commandLine;
162 }
163
164 return ClonkMain(GetModuleHandle(nullptr), 0, argc, argv, commandLine);
165}
166
167#else
168
169#include <unistd.h>
170#include <fcntl.h>
171#include <signal.h>
172
173#ifndef _WIN32
174
175#include <execinfo.h>
176
177#define HAVE_EXECINFO_H
178
179static void crash_handler(int signo)
180{
181 int logfd = STDERR_FILENO;
182 for (;;)
183 {
184 // Print out the signal
185 write(fd: logfd, C4VERSION ": Caught signal ", n: sizeof(C4VERSION ": Caught signal ") - 1);
186 switch (signo)
187 {
188 case SIGBUS: write(fd: logfd, buf: "SIGBUS", n: sizeof("SIGBUS") - 1); break;
189 case SIGILL: write(fd: logfd, buf: "SIGILL", n: sizeof("SIGILL") - 1); break;
190 case SIGSEGV: write(fd: logfd, buf: "SIGSEGV", n: sizeof("SIGSEGV") - 1); break;
191 case SIGABRT: write(fd: logfd, buf: "SIGABRT", n: sizeof("SIGABRT") - 1); break;
192 case SIGINT: write(fd: logfd, buf: "SIGINT", n: sizeof("SIGINT") - 1); break;
193 case SIGQUIT: write(fd: logfd, buf: "SIGQUIT", n: sizeof("SIGQUIT") - 1); break;
194 case SIGFPE: write(fd: logfd, buf: "SIGFPE", n: sizeof("SIGFPE") - 1); break;
195 case SIGTERM: write(fd: logfd, buf: "SIGTERM", n: sizeof("SIGTERM") - 1); break;
196 }
197 write(fd: logfd, buf: "\n", n: sizeof("\n") - 1);
198 if (logfd == STDERR_FILENO) logfd = GetLogFD();
199 else break;
200 if (logfd < 0) break;
201 }
202 // Get the backtrace
203 void *stack[100];
204 int count = backtrace(array: stack, size: 100);
205 // Print it out
206 backtrace_symbols_fd(array: stack, size: count, STDERR_FILENO);
207 // Also to the log file
208 if (logfd >= 0)
209 backtrace_symbols_fd(array: stack, size: count, fd: logfd);
210
211 signal(sig: signo, SIG_DFL);
212 raise(sig: signo);
213}
214
215#endif
216
217#ifdef __APPLE__
218void restart(char *[]); // MacUtility.mm
219#else
220static void restart(char *argv[])
221{
222 // Close all file descriptors except stdin, stdout, stderr
223 int open_max = sysconf(_SC_OPEN_MAX);
224 for (int fd = 4; fd < open_max; fd++)
225 fcntl(fd: fd, F_SETFD, FD_CLOEXEC);
226 // Execute the new engine
227 execlp(file: argv[0], arg: argv[0], static_cast<char *>(0));
228}
229#endif
230
231int main(int argc, char *argv[])
232{
233#ifdef __APPLE__
234 std::string enginePath{argv[0]};
235 if (const auto originalPath = GetNonTranslocatedPath(argv[0]); originalPath)
236 {
237 enginePath = *originalPath;
238 }
239 chdir(dirname(dirname(dirname(dirname(enginePath.data())))));
240#elif defined(__linux__)
241 if (std::getenv(name: "APPIMAGE"))
242 {
243 const auto argv0 = std::getenv(name: "ARGV0");
244 if (argv0)
245 {
246 argv[0] = argv0;
247 }
248 }
249#endif
250
251 if (!geteuid())
252 {
253 printf(format: "Do not run %s as root!\n", argc ? argv[0] : "this program");
254 return C4XRV_Failure;
255 }
256#ifdef HAVE_EXECINFO_H
257 // Set up debugging facilities
258 signal(SIGBUS, handler: crash_handler);
259 signal(SIGILL, handler: crash_handler);
260 signal(SIGSEGV, handler: crash_handler);
261 signal(SIGABRT, handler: crash_handler);
262 signal(SIGINT, handler: crash_handler);
263 signal(SIGQUIT, handler: crash_handler);
264 signal(SIGFPE, handler: crash_handler);
265 signal(SIGTERM, handler: crash_handler);
266#endif
267
268 // FIXME: This should only be done in developer mode.
269#ifdef WITH_DEVELOPER_MODE
270 gdk_set_allowed_backends(backends: "x11");
271 gtk_init(argc: &argc, argv: &argv);
272#endif
273
274 // Init application
275 try
276 {
277 Application.Init(argc, argv);
278 }
279 catch (const CStdApp::StartupException &e)
280 {
281 Application.Clear();
282 fprintf(stderr, format: "%s\n", e.what());
283
284#ifdef WITH_DEVELOPER_MODE
285 GtkWidget *const dialog{gtk_message_dialog_new(parent: nullptr, flags: GTK_DIALOG_DESTROY_WITH_PARENT, type: GTK_MESSAGE_ERROR, buttons: GTK_BUTTONS_OK, message_format: "%s", e.what())};
286 gtk_dialog_run(GTK_DIALOG(dialog));
287 gtk_widget_destroy(widget: dialog);
288#endif
289 return C4XRV_Failure;
290 }
291
292 // Execute application
293 Application.Run();
294 // free app stuff
295 Application.Clear();
296 if (Application.restartAtEnd) restart(argv);
297 // Return exit code
298 return C4XRV_Completed;
299}
300
301#endif
302