| 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 | |
| 51 | C4Application Application; |
| 52 | C4Console Console; |
| 53 | C4FullScreen FullScreen; |
| 54 | C4Game Game; |
| 55 | C4Config Config; |
| 56 | |
| 57 | #ifdef _WIN32 |
| 58 | |
| 59 | void InstallCrashHandler(); |
| 60 | |
| 61 | int 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 | |
| 127 | int WINAPI WinMain(HINSTANCE hInst, |
| 128 | HINSTANCE hPrevInstance, |
| 129 | LPSTR lpszCmdParam, |
| 130 | int nCmdShow) |
| 131 | { |
| 132 | return ClonkMain(hInst, nCmdShow, __argc, __argv, lpszCmdParam); |
| 133 | } |
| 134 | |
| 135 | int 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 | |
| 179 | static 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__ |
| 218 | void restart(char *[]); // MacUtility.mm |
| 219 | #else |
| 220 | static 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 | |
| 231 | int 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 | |