1 /*
2 This file is part of Warzone 2100.
3 Copyright (C) 1999-2004 Eidos Interactive
4 Copyright (C) 2005-2020 Warzone 2100 Project
5
6 Warzone 2100 is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10
11 Warzone 2100 is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Warzone 2100; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20 /** @file
21 * The main file that launches the game and starts up everything.
22 */
23
24 // Get platform defines before checking for them!
25 #include "lib/framework/wzapp.h"
26
27 #if defined(WZ_OS_WIN)
28 # if defined( _MSC_VER )
29 // Silence warning when using MSVC + the Windows 7 SDK (required for XP compatibility)
30 // warning C4091: 'typedef ': ignored on left of 'tagGPFIDL_FLAGS' when no variable is declared
31 #pragma warning( push )
32 #pragma warning( disable : 4091 )
33 # endif
34 # include <shlobj.h> /* For SHGetFolderPath */
35 # if defined( _MSC_VER )
36 #pragma warning( pop )
37 # endif
38 # include <shellapi.h> /* CommandLineToArgvW */
39 #elif defined(WZ_OS_UNIX)
40 # include <errno.h>
41 #endif // WZ_OS_WIN
42
43 #include "lib/framework/input.h"
44 #include "lib/framework/physfs_ext.h"
45 #include "lib/framework/wzpaths.h"
46 #include "lib/framework/wztime.h"
47 #include "lib/exceptionhandler/exceptionhandler.h"
48 #include "lib/exceptionhandler/dumpinfo.h"
49
50 #include "lib/sound/playlist.h"
51 #include "lib/gamelib/gtime.h"
52 #include "lib/ivis_opengl/pieblitfunc.h"
53 #include "lib/ivis_opengl/piestate.h"
54 #include "lib/ivis_opengl/piepalette.h"
55 #include "lib/ivis_opengl/piemode.h"
56 #include "lib/ivis_opengl/screen.h"
57 #include "lib/netplay/netplay.h"
58 #include "lib/sound/audio.h"
59 #include "lib/sound/cdaudio.h"
60
61 #include "clparse.h"
62 #include "challenge.h"
63 #include "configuration.h"
64 #include "display.h"
65 #include "display3d.h"
66 #include "frontend.h"
67 #include "game.h"
68 #include "init.h"
69 #include "levels.h"
70 #include "lighting.h"
71 #include "loadsave.h"
72 #include "loop.h"
73 #include "mission.h"
74 #include "modding.h"
75 #include "multiplay.h"
76 #include "notifications.h"
77 #include "qtscript.h"
78 #include "research.h"
79 #include "seqdisp.h"
80 #include "warzoneconfig.h"
81 #include "main.h"
82 #include "wrappers.h"
83 #include "version.h"
84 #include "map.h"
85 #include "keybind.h"
86 #include "random.h"
87 #include "urlrequest.h"
88 #include <time.h>
89 #include <LaunchInfo.h>
90 #include <sodium.h>
91 #include "updatemanager.h"
92 #include "activity.h"
93 #if defined(ENABLE_DISCORD)
94 #include "integrations/wzdiscordrpc.h"
95 #endif
96
97 #if defined(WZ_OS_UNIX)
98 # include <signal.h>
99 # include <time.h>
100 #endif
101
102 #if defined(WZ_OS_MAC)
103 // NOTE: Moving these defines is likely to (and has in the past) break the mac builds
104 # include <CoreServices/CoreServices.h>
105 # include <unistd.h>
106 # include "lib/framework/cocoa_wrapper.h"
107 #endif // WZ_OS_MAC
108
109 /* Always use fallbacks on Windows */
110 #if defined(WZ_OS_WIN)
111 # undef WZ_DATADIR
112 #endif
113
114 #if !defined(WZ_DATADIR)
115 # define WZ_DATADIR "data"
116 #endif
117
118
119 enum FOCUS_STATE
120 {
121 FOCUS_OUT, // Window does not have the focus
122 FOCUS_IN, // Window has got the focus
123 };
124
125 bool customDebugfile = false; // Default false: user has NOT specified where to store the stdout/err file.
126
127 char datadir[PATH_MAX] = ""; // Global that src/clparse.c:ParseCommandLine can write to, so it can override the default datadir on runtime. Needs to be empty on startup for ParseCommandLine to work!
128 char configdir[PATH_MAX] = ""; // specifies custom USER directory. Same rules apply as datadir above.
129 char rulesettag[40] = "";
130
131 //flag to indicate when initialisation is complete
132 bool gameInitialised = false;
133 char SaveGamePath[PATH_MAX];
134 char ScreenDumpPath[PATH_MAX];
135 char MultiCustomMapsPath[PATH_MAX];
136 char MultiPlayersPath[PATH_MAX];
137 char KeyMapPath[PATH_MAX];
138 // Start game in title mode:
139 static GS_GAMEMODE gameStatus = GS_TITLE_SCREEN;
140 // Status of the gameloop
141 static GAMECODE gameLoopStatus = GAMECODE_CONTINUE;
142 static FOCUS_STATE focusState = FOCUS_IN;
143
144 #if defined(WZ_OS_UNIX)
145 static bool ignoredSIGPIPE = false;
146 #endif
147
148
149 #if defined(WZ_OS_WIN)
150
151 #define WIN_MAX_EXTENDED_PATH 32767
152
153 // Gets the full path to the application executable (UTF-16)
getCurrentApplicationPath_WIN()154 static std::wstring getCurrentApplicationPath_WIN()
155 {
156 // On Windows, use GetModuleFileNameW to obtain the full path to the current EXE
157
158 std::vector<wchar_t> buffer(WIN_MAX_EXTENDED_PATH + 1, 0);
159 DWORD moduleFileNameLen = GetModuleFileNameW(NULL, &buffer[0], buffer.size() - 1);
160 DWORD lastError = GetLastError();
161 if ((moduleFileNameLen == 0) && (lastError != ERROR_SUCCESS))
162 {
163 // GetModuleFileName failed
164 debug(LOG_ERROR, "GetModuleFileName failed: %lu", moduleFileNameLen);
165 return std::wstring();
166 }
167 else if (moduleFileNameLen > (buffer.size() - 1))
168 {
169 debug(LOG_ERROR, "GetModuleFileName returned a length: %lu >= buffer length: %zu", moduleFileNameLen, buffer.size());
170 return std::wstring();
171 }
172
173 // Because Windows XP's GetModuleFileName does not guarantee null-termination,
174 // always append a null-terminator
175 buffer[moduleFileNameLen] = 0;
176
177 return std::wstring(buffer.data());
178 }
179
180 // Gets the full path to the folder that contains the application executable (UTF-16)
getCurrentApplicationFolder_WIN()181 static std::wstring getCurrentApplicationFolder_WIN()
182 {
183 std::wstring applicationExecutablePath = getCurrentApplicationPath_WIN();
184 if (applicationExecutablePath.empty())
185 {
186 return std::wstring();
187 }
188
189 // Find the position of the last slash in the application executable path
190 size_t lastSlash = applicationExecutablePath.find_last_of(L"\\/", std::wstring::npos);
191 if (lastSlash == std::wstring::npos)
192 {
193 // Did not find a path separator - does not appear to be a valid application executable path?
194 debug(LOG_ERROR, "Unable to find path separator in application executable path");
195 return std::wstring();
196 }
197
198 // Trim off the executable name
199 return applicationExecutablePath.substr(0, lastSlash);
200 }
201 #endif
202
203 // Gets the full path to the folder that contains the application executable (UTF-8)
getCurrentApplicationFolder()204 static std::string getCurrentApplicationFolder()
205 {
206 #if defined(WZ_OS_WIN)
207 std::wstring applicationFolderPath_utf16 = getCurrentApplicationFolder_WIN();
208
209 // Convert the UTF-16 to UTF-8
210 int outputLength = WideCharToMultiByte(CP_UTF8, 0, applicationFolderPath_utf16.c_str(), -1, NULL, 0, NULL, NULL);
211 if (outputLength <= 0)
212 {
213 debug(LOG_ERROR, "Encoding conversion error.");
214 return std::string();
215 }
216 std::vector<char> u8_buffer(outputLength, 0);
217 if (WideCharToMultiByte(CP_UTF8, 0, applicationFolderPath_utf16.c_str(), -1, &u8_buffer[0], outputLength, NULL, NULL) == 0)
218 {
219 debug(LOG_ERROR, "Encoding conversion error.");
220 return std::string();
221 }
222
223 return std::string(u8_buffer.data());
224 #else
225 // Not yet implemented for this platform
226 return std::string();
227 #endif
228 }
229
230 // Gets the full path to the prefix of the folder that contains the application executable (UTF-8)
getCurrentApplicationFolderPrefix()231 static std::string getCurrentApplicationFolderPrefix()
232 {
233 // Remove the last path component
234 std::string appPath = getCurrentApplicationFolder();
235 if (appPath.empty())
236 {
237 return appPath;
238 }
239
240 // Remove trailing path separators (slashes)
241 while (!appPath.empty() && (appPath.back() == '\\' || appPath.back() == '/'))
242 {
243 appPath.pop_back();
244 }
245
246 // Find the position of the last slash in the application folder
247 size_t lastSlash = appPath.find_last_of("\\/", std::string::npos);
248 if (lastSlash == std::string::npos)
249 {
250 // Did not find a path separator - does not appear to be a valid app folder?
251 debug(LOG_ERROR, "Unable to find path separator in application executable path");
252 return std::string();
253 }
254
255 // Trim off the last path component
256 return appPath.substr(0, lastSlash);
257 }
258
isPortableMode()259 static bool isPortableMode()
260 {
261 static bool _checkedMode = false;
262 static bool _isPortableMode = false;
263 if (!_checkedMode)
264 {
265 #if defined(WZ_OS_WIN)
266 // On Windows, check for the presence of a ".portable" file in the same directory as the application EXE
267 std::wstring portableFilePath = getCurrentApplicationFolder_WIN();
268 portableFilePath += L"\\.portable";
269
270 if (GetFileAttributesW(portableFilePath.c_str()) != INVALID_FILE_ATTRIBUTES)
271 {
272 // A .portable file exists in the application directory
273 debug(LOG_WARNING, ".portable file detected - enabling portable mode");
274 _isPortableMode = true;
275 }
276 #else
277 // Not yet implemented for this platform.
278 #endif
279 _checkedMode = true;
280 }
281 return _isPortableMode;
282 }
283
284 /*!
285 * Retrieves the current working directory and copies it into the provided output buffer
286 * \param[out] dest the output buffer to put the current working directory in
287 * \param size the size (in bytes) of \c dest
288 * \return true on success, false if an error occurred (and dest doesn't contain a valid directory)
289 */
290 #if !defined(WZ_PHYSFS_2_1_OR_GREATER)
getCurrentDir(char * const dest,size_t const size)291 static bool getCurrentDir(char *const dest, size_t const size)
292 {
293 #if defined(WZ_OS_UNIX)
294 if (getcwd(dest, size) == nullptr)
295 {
296 if (errno == ERANGE)
297 {
298 debug(LOG_ERROR, "The buffer to contain our current directory is too small (%u bytes and more needed)", (unsigned int)size);
299 }
300 else
301 {
302 debug(LOG_ERROR, "getcwd failed: %s", strerror(errno));
303 }
304
305 return false;
306 }
307 #elif defined(WZ_OS_WIN)
308 wchar_t tmpWStr[PATH_MAX];
309 const int len = GetCurrentDirectoryW(PATH_MAX, tmpWStr);
310
311 if (len == 0)
312 {
313 // Retrieve Windows' error number
314 const int err = GetLastError();
315 char *err_string = NULL;
316
317 // Retrieve a string describing the error number (uses LocalAlloc() to allocate memory for err_string)
318 FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, err, 0, (char *)&err_string, 0, NULL);
319
320 // Print an error message with the above description
321 debug(LOG_ERROR, "GetCurrentDirectory failed (error code: %d): %s", err, err_string);
322
323 // Free our chunk of memory FormatMessageA gave us
324 LocalFree(err_string);
325
326 return false;
327 }
328 else if (len > size)
329 {
330 debug(LOG_ERROR, "The buffer to contain our current directory is too small (%u bytes and %d needed)", (unsigned int)size, len);
331
332 return false;
333 }
334 if (WideCharToMultiByte(CP_UTF8, 0, tmpWStr, -1, dest, size, NULL, NULL) == 0)
335 {
336 dest[0] = '\0';
337 debug(LOG_ERROR, "Encoding conversion error.");
338 return false;
339 }
340 #else
341 # error "Provide an implementation here to copy the current working directory in 'dest', which is 'size' bytes large."
342 #endif
343
344 // If we got here everything went well
345 return true;
346 }
347 #endif
348
349 // Fallback method for earlier PhysFS verions that do not support PHYSFS_getPrefDir
350 // Importantly, this creates the folders if they do not exist
351 #if !defined(WZ_PHYSFS_2_1_OR_GREATER)
getPlatformPrefDir_Fallback(const char * org,const char * app)352 static std::string getPlatformPrefDir_Fallback(const char *org, const char *app)
353 {
354 WzString basePath;
355 WzString appendPath;
356 char tmpstr[PATH_MAX] = { '\0' };
357 const size_t size = sizeof(tmpstr);
358 #if defined(WZ_OS_WIN)
359 wchar_t tmpWStr[MAX_PATH];
360
361 if (SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_APPDATA | CSIDL_FLAG_CREATE, NULL, SHGFP_TYPE_CURRENT, tmpWStr)))
362 {
363 if (WideCharToMultiByte(CP_UTF8, 0, tmpWStr, -1, tmpstr, size, NULL, NULL) == 0)
364 {
365 debug(LOG_FATAL, "Config directory encoding conversion error.");
366 exit(1);
367 }
368 basePath = WzString::fromUtf8(tmpstr);
369
370 appendPath = WzString();
371 // Must append org\app to APPDATA path
372 appendPath += org;
373 appendPath += PHYSFS_getDirSeparator();
374 appendPath += app;
375 }
376 else
377 #elif defined(WZ_OS_MAC)
378 if (cocoaGetApplicationSupportDir(tmpstr, size))
379 {
380 basePath = WzString::fromUtf8(tmpstr);
381 appendPath = WzString::fromUtf8(app);
382 }
383 else
384 #elif defined(WZ_OS_UNIX)
385 // Following PhysFS, use XDG's base directory spec, even if not on Linux.
386 // Reference: https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
387 const char *envPath = getenv("XDG_DATA_HOME");
388 if (envPath == nullptr)
389 {
390 // XDG_DATA_HOME isn't defined
391 // Use HOME, and append ".local/share/" to match XDG's base directory spec
392 envPath = getenv("HOME");
393
394 if (envPath == nullptr)
395 {
396 // On PhysFS < 2.1, fall-back to using PHYSFS_getUserDir() if HOME isn't defined
397 debug(LOG_INFO, "HOME environment variable isn't defined - falling back to PHYSFS_getUserDir()");
398 envPath = PHYSFS_getUserDir();
399 }
400
401 appendPath = WzString(".local") + PHYSFS_getDirSeparator() + "share";
402 }
403
404 if (envPath != nullptr)
405 {
406 basePath = WzString::fromUtf8(envPath);
407
408 if (!appendPath.isEmpty())
409 {
410 appendPath += PHYSFS_getDirSeparator();
411 }
412 appendPath += app;
413 }
414 else
415 #else
416 // On PhysFS < 2.1, fall-back to using PHYSFS_getUserDir() for other OSes
417 if (PHYSFS_getUserDir())
418 {
419 basePath = WzString::fromUtf8(PHYSFS_getUserDir());
420 appendPath = WzString::fromUtf8(app);
421 }
422 else
423 #endif
424 if (getCurrentDir(tmpstr, size))
425 {
426 basePath = WzString::fromUtf8(tmpstr);
427 appendPath = WzString::fromUtf8(app);
428 }
429 else
430 {
431 debug(LOG_FATAL, "Can't get home / prefs directory?");
432 abort();
433 }
434
435 // Create the folders within the basePath if they don't exist
436
437 if (!PHYSFS_setWriteDir(basePath.toUtf8().c_str())) // Workaround for PhysFS not creating the writedir as expected.
438 {
439 debug(LOG_FATAL, "Error setting write directory to \"%s\": %s",
440 basePath.toUtf8().c_str(), WZ_PHYSFS_getLastError());
441 exit(1);
442 }
443
444 WzString currentBasePath = basePath;
445 const std::vector<WzString> appendPaths = appendPath.split(PHYSFS_getDirSeparator());
446 for (const auto &folder : appendPaths)
447 {
448 if (!PHYSFS_mkdir(folder.toUtf8().c_str()))
449 {
450 debug(LOG_FATAL, "Error creating directory \"%s\" in \"%s\": %s",
451 folder.toUtf8().c_str(), PHYSFS_getWriteDir(), WZ_PHYSFS_getLastError());
452 exit(1);
453 }
454
455 currentBasePath += PHYSFS_getDirSeparator();
456 currentBasePath += folder;
457
458 if (!PHYSFS_setWriteDir(currentBasePath.toUtf8().c_str())) // Workaround for PhysFS not creating the writedir as expected.
459 {
460 debug(LOG_FATAL, "Error setting write directory to \"%s\": %s",
461 currentBasePath.toUtf8().c_str(), WZ_PHYSFS_getLastError());
462 exit(1);
463 }
464 }
465
466 return (basePath + PHYSFS_getDirSeparator() + appendPath + PHYSFS_getDirSeparator()).toUtf8();
467 }
468 #endif
469
470 // Retrieves the appropriate storage directory for application-created files / prefs
471 // (Ensures the directory exists. Creates folders if necessary.)
getPlatformPrefDir(const char * org,const std::string & app)472 static std::string getPlatformPrefDir(const char * org, const std::string &app)
473 {
474 if (isPortableMode())
475 {
476 // When isPortableMode is true, the config dir should be stored in the same prefix as the app's bindir.
477 // i.e. If the app executable path is: <prefix>/bin/warzone2100.exe
478 // the config directory should be: <prefix>/<app>/
479 std::string prefixPath = getCurrentApplicationFolderPrefix();
480 if (prefixPath.empty())
481 {
482 // Failed to get the current application folder
483 debug(LOG_FATAL, "Error getting the current application folder prefix - unable to proceed with portable mode");
484 exit(1);
485 }
486
487 std::string appendPath = app;
488
489 // Create the folders within the prefixPath if they don't exist
490 if (!PHYSFS_setWriteDir(prefixPath.c_str())) // Workaround for PhysFS not creating the writedir as expected.
491 {
492 debug(LOG_FATAL, "Error setting write directory to \"%s\": %s",
493 prefixPath.c_str(), WZ_PHYSFS_getLastError());
494 exit(1);
495 }
496
497 if (!PHYSFS_mkdir(appendPath.c_str()))
498 {
499 debug(LOG_FATAL, "Error creating directory \"%s\" in \"%s\": %s",
500 appendPath.c_str(), PHYSFS_getWriteDir(), WZ_PHYSFS_getLastError());
501 exit(1);
502 }
503
504 return prefixPath + PHYSFS_getDirSeparator() + appendPath + PHYSFS_getDirSeparator();
505 }
506
507 #if defined(WZ_PHYSFS_2_1_OR_GREATER)
508 const char * prefsDir = PHYSFS_getPrefDir(org, app.c_str());
509 if (prefsDir == nullptr)
510 {
511 debug(LOG_FATAL, "Failed to obtain prefs directory: %s", WZ_PHYSFS_getLastError());
512 exit(1);
513 }
514 return std::string(prefsDir) + PHYSFS_getDirSeparator();
515 #else
516 // PHYSFS_getPrefDir is not available - use fallback method (which requires OS-specific code)
517 std::string prefDir = getPlatformPrefDir_Fallback(org, app.c_str());
518 if (prefDir.empty())
519 {
520 debug(LOG_FATAL, "Failed to obtain prefs directory (fallback)");
521 exit(1);
522 }
523 return prefDir;
524 #endif // defined(WZ_PHYSFS_2_1_OR_GREATER)
525 }
526
endsWith(std::string const & fullString,std::string const & endString)527 bool endsWith (std::string const &fullString, std::string const &endString) {
528 if (fullString.length() >= endString.length()) {
529 return (0 == fullString.compare (fullString.length() - endString.length(), endString.length(), endString));
530 } else {
531 return false;
532 }
533 }
534
initialize_ConfigDir()535 static void initialize_ConfigDir()
536 {
537 std::string configDir;
538
539 if (strlen(configdir) == 0)
540 {
541 configDir = getPlatformPrefDir("Warzone 2100 Project", version_getVersionedAppDirFolderName());
542 }
543 else
544 {
545 configDir = std::string(configdir);
546
547 // Make sure that we have a directory separator at the end of the string
548 if (!endsWith(configDir, PHYSFS_getDirSeparator()))
549 {
550 configDir += PHYSFS_getDirSeparator();
551 }
552
553 debug(LOG_WZ, "Using custom configuration directory: %s", configDir.c_str());
554 }
555
556 if (!PHYSFS_setWriteDir(configDir.c_str())) // Workaround for PhysFS not creating the writedir as expected.
557 {
558 debug(LOG_FATAL, "Error setting write directory to \"%s\": %s",
559 configDir.c_str(), WZ_PHYSFS_getLastError());
560 exit(1);
561 }
562
563 if (!OverrideRPTDirectory(configDir.c_str()))
564 {
565 // since it failed, we just use our default path, and not the user supplied one.
566 debug(LOG_ERROR, "Error setting exception handler to use directory %s", configDir.c_str());
567 }
568
569
570 // Config dir first so we always see what we write
571 PHYSFS_mount(PHYSFS_getWriteDir(), NULL, PHYSFS_PREPEND);
572
573 // Do not follow symlinks *inside* search paths / archives
574 PHYSFS_permitSymbolicLinks(0);
575
576 debug(LOG_WZ, "Write dir: %s", PHYSFS_getWriteDir());
577 debug(LOG_WZ, "Base dir: %s", PHYSFS_getBaseDir());
578 }
579
580
581 /*!
582 * Initialize the PhysicsFS library.
583 */
initialize_PhysicsFS(const char * argv_0)584 static void initialize_PhysicsFS(const char *argv_0)
585 {
586 int result = PHYSFS_init(argv_0);
587
588 if (!result)
589 {
590 debug(LOG_FATAL, "There was a problem trying to init Physfs. Error was %s", WZ_PHYSFS_getLastError());
591 exit(-1);
592 }
593 }
594
check_Physfs()595 static void check_Physfs()
596 {
597 const PHYSFS_ArchiveInfo **i;
598 bool zipfound = false;
599 PHYSFS_Version compiled;
600 PHYSFS_Version linked;
601
602 PHYSFS_VERSION(&compiled);
603 PHYSFS_getLinkedVersion(&linked);
604
605 debug(LOG_WZ, "Compiled against PhysFS version: %d.%d.%d",
606 compiled.major, compiled.minor, compiled.patch);
607 debug(LOG_WZ, "Linked against PhysFS version: %d.%d.%d",
608 linked.major, linked.minor, linked.patch);
609 if (linked.major < 2)
610 {
611 debug(LOG_FATAL, "At least version 2 of PhysicsFS required!");
612 exit(-1);
613 }
614 if (linked.major == 2 && linked.minor == 0 && linked.patch == 2)
615 {
616 debug(LOG_ERROR, "You have PhysicsFS 2.0.2, which is buggy. You may experience random errors/crashes due to spuriously missing files.");
617 debug(LOG_ERROR, "Please upgrade/downgrade PhysicsFS to a different version, such as 2.0.3 or 2.0.1.");
618 }
619
620 for (i = PHYSFS_supportedArchiveTypes(); *i != nullptr; i++)
621 {
622 debug(LOG_WZ, "[**] Supported archive(s): [%s], which is [%s].", (*i)->extension, (*i)->description);
623 if (!strncasecmp("zip", (*i)->extension, 3) && !zipfound)
624 {
625 zipfound = true;
626 }
627 }
628 if (!zipfound)
629 {
630 debug(LOG_FATAL, "Your Physfs wasn't compiled with zip support. Please recompile Physfs with zip support. Exiting program.");
631 exit(-1);
632 }
633 }
634
635
636 /*!
637 * \brief Adds default data dirs
638 *
639 * Priority:
640 * Lower loads first. Current:
641 * --datadir > User's home dir > source tree data > AutoPackage > BaseDir > DEFAULT_DATADIR
642 *
643 * Only --datadir and home dir are always examined. Others only if data still not found.
644 *
645 * We need ParseCommandLine, before we can add any mods...
646 *
647 * \sa rebuildSearchPath
648 */
scanDataDirs()649 static void scanDataDirs()
650 {
651 #if defined(WZ_OS_MAC)
652 // version-independent location for video files
653 registerSearchPath("/Library/Application Support/Warzone 2100/", 1);
654 #endif
655
656 // Commandline supplied datadir
657 if (strlen(datadir) != 0)
658 {
659 registerSearchPath(datadir, 1);
660 }
661
662 // User's home dir
663 registerSearchPath(PHYSFS_getWriteDir(), 2);
664 rebuildSearchPath(mod_multiplay, true);
665
666 #if !defined(WZ_OS_MAC)
667 // Check PREFIX-based paths
668 std::string tmpstr;
669
670 // Find out which PREFIX we are in...
671 std::string prefix = getWZInstallPrefix();
672 std::string dirSeparator(PHYSFS_getDirSeparator());
673
674 if (!PHYSFS_exists("gamedesc.lev"))
675 {
676 // Data in source tree (<prefix>/data/)
677 tmpstr = prefix + dirSeparator + "data" + dirSeparator;
678 registerSearchPath(tmpstr.c_str(), 3);
679 rebuildSearchPath(mod_multiplay, true);
680
681 if (!PHYSFS_exists("gamedesc.lev"))
682 {
683 // Program dir
684 registerSearchPath(PHYSFS_getBaseDir(), 4);
685 rebuildSearchPath(mod_multiplay, true);
686
687 if (!PHYSFS_exists("gamedesc.lev"))
688 {
689 // Guessed fallback default datadir on Unix
690 std::string wzDataDir = WZ_DATADIR;
691 if(!wzDataDir.empty())
692 {
693 #ifndef WZ_DATADIR_ISABSOLUTE
694 // Treat WZ_DATADIR as a relative path - append to the install PREFIX
695 tmpstr = prefix + dirSeparator + wzDataDir;
696 registerSearchPath(tmpstr.c_str(), 5);
697 rebuildSearchPath(mod_multiplay, true);
698 #else
699 // Treat WZ_DATADIR as an absolute path, and use directly
700 registerSearchPath(wzDataDir.c_str(), 5);
701 rebuildSearchPath(mod_multiplay, true);
702 #endif
703 }
704
705 if (!PHYSFS_exists("gamedesc.lev"))
706 {
707 // Relocation for AutoPackage (<prefix>/share/warzone2100/)
708 tmpstr = prefix + dirSeparator + "share" + dirSeparator + "warzone2100" + dirSeparator;
709 registerSearchPath(tmpstr.c_str(), 6);
710 rebuildSearchPath(mod_multiplay, true);
711
712 if (!PHYSFS_exists("gamedesc.lev"))
713 {
714 // Guessed fallback default datadir on Unix
715 // TEMPORARY: Fallback to ensure old WZ_DATADIR behavior as a last-resort
716 // This is only present for the benefit of the automake build toolchain.
717 registerSearchPath(WZ_DATADIR, 7);
718 rebuildSearchPath(mod_multiplay, true);
719 }
720 }
721 }
722 }
723 }
724 #endif
725
726 #ifdef WZ_OS_MAC
727 if (!PHYSFS_exists("gamedesc.lev"))
728 {
729 CFURLRef resourceURL = CFBundleCopyResourcesDirectoryURL(CFBundleGetMainBundle());
730 char resourcePath[PATH_MAX];
731 if (CFURLGetFileSystemRepresentation(resourceURL, true,
732 (UInt8 *) resourcePath,
733 PATH_MAX))
734 {
735 WzString resourceDataPath(resourcePath);
736 resourceDataPath += "/data";
737 registerSearchPath(resourceDataPath.toUtf8().c_str(), 8);
738 rebuildSearchPath(mod_multiplay, true);
739 }
740 else
741 {
742 debug(LOG_ERROR, "Could not change to resources directory.");
743 }
744
745 if (resourceURL != NULL)
746 {
747 CFRelease(resourceURL);
748 }
749 }
750 #endif
751
752 /** Debugging and sanity checks **/
753
754 printSearchPath();
755
756 if (PHYSFS_exists("gamedesc.lev"))
757 {
758 debug(LOG_WZ, "gamedesc.lev found at %s", WZ_PHYSFS_getRealDir_String("gamedesc.lev").c_str());
759 }
760 else
761 {
762 debug(LOG_FATAL, "Could not find game data. Aborting.");
763 exit(1);
764 }
765 }
766
767
768 /***************************************************************************
769 Make a directory in write path and set a variable to point to it.
770 ***************************************************************************/
make_dir(char * dest,const char * dirname,const char * subdir)771 static void make_dir(char *dest, const char *dirname, const char *subdir)
772 {
773 strcpy(dest, dirname);
774 if (subdir != nullptr)
775 {
776 strcat(dest, "/");
777 strcat(dest, subdir);
778 }
779 {
780 size_t l = strlen(dest);
781
782 if (dest[l - 1] != '/')
783 {
784 dest[l] = '/';
785 dest[l + 1] = '\0';
786 }
787 }
788 if (!PHYSFS_mkdir(dest))
789 {
790 debug(LOG_FATAL, "Unable to create directory \"%s\" in write dir \"%s\"!",
791 dest, PHYSFS_getWriteDir());
792 exit(EXIT_FAILURE);
793 }
794 }
795
796
797 /*!
798 * Preparations before entering the title (mainmenu) loop
799 * Would start the timer in an event based mainloop
800 */
startTitleLoop()801 static void startTitleLoop()
802 {
803 SetGameMode(GS_TITLE_SCREEN);
804
805 initLoadingScreen(true);
806 if (!frontendInitialise("wrf/frontend.wrf"))
807 {
808 debug(LOG_FATAL, "Shutting down after failure");
809 exit(EXIT_FAILURE);
810 }
811 closeLoadingScreen();
812 }
813
814
815 /*!
816 * Shutdown/cleanup after the title (mainmenu) loop
817 * Would stop the timer
818 */
stopTitleLoop()819 static void stopTitleLoop()
820 {
821 if (!frontendShutdown())
822 {
823 debug(LOG_FATAL, "Shutting down after failure");
824 exit(EXIT_FAILURE);
825 }
826 }
827
setCDAudioForCurrentGameMode()828 static void setCDAudioForCurrentGameMode()
829 {
830 auto gameMode = ActivityManager::instance().getCurrentGameMode();
831 switch (gameMode)
832 {
833 case ActivitySink::GameMode::CAMPAIGN:
834 cdAudio_SetGameMode(MusicGameMode::CAMPAIGN);
835 break;
836 case ActivitySink::GameMode::CHALLENGE:
837 cdAudio_SetGameMode(MusicGameMode::CHALLENGE);
838 break;
839 case ActivitySink::GameMode::SKIRMISH:
840 cdAudio_SetGameMode(MusicGameMode::SKIRMISH);
841 break;
842 case ActivitySink::GameMode::MULTIPLAYER:
843 cdAudio_SetGameMode(MusicGameMode::MULTIPLAYER);
844 break;
845 default:
846 debug(LOG_ERROR, "Unhandled started game mode for cd audio: %u", (unsigned int)gameMode);
847 break;
848 }
849 }
850
851 /*!
852 * Preparations before entering the game loop
853 * Would start the timer in an event based mainloop
854 */
startGameLoop()855 static void startGameLoop()
856 {
857 SetGameMode(GS_NORMAL);
858 initLoadingScreen(true);
859
860 ActivityManager::instance().startingGame();
861
862 // set up CD audio for game mode
863 setCDAudioForCurrentGameMode();
864
865 // Not sure what aLevelName is, in relation to game.map. But need to use aLevelName here, to be able to start the right map for campaign, and need game.hash, to start the right non-campaign map, if there are multiple identically named maps.
866 if (!levLoadData(aLevelName, &game.hash, nullptr, GTYPE_SCENARIO_START))
867 {
868 debug(LOG_FATAL, "Shutting down after failure");
869 exit(EXIT_FAILURE);
870 }
871
872 screen_StopBackDrop();
873 closeLoadingScreen();
874
875 // Trap the cursor if cursor snapping is enabled
876 if (war_GetTrapCursor())
877 {
878 wzGrabMouse();
879 }
880
881 // Disable resizable windows if it's a multiplayer game
882 if (runningMultiplayer())
883 {
884 // This is because the main loop gets frozen while the window resizing / edge dragging occurs
885 // which effectively pauses the game, and pausing is otherwise disabled in multiplayer games.
886 // FIXME: Figure out a workaround?
887 wzSetWindowIsResizable(false);
888 }
889
890 // set a flag for the trigger/event system to indicate initialisation is complete
891 gameInitialised = true;
892
893 if (challengeActive)
894 {
895 addMissionTimerInterface();
896 }
897 triggerEvent(TRIGGER_START_LEVEL);
898 screen_disableMapPreview();
899 }
900
901
902 /*!
903 * Shutdown/cleanup after the game loop
904 * Would stop the timer
905 */
stopGameLoop()906 static void stopGameLoop()
907 {
908 clearInfoMessages(); // clear CONPRINTF messages before each new game/mission
909 if (gameLoopStatus != GAMECODE_NEWLEVEL)
910 {
911 clearBlueprints();
912 initLoadingScreen(true); // returning to f.e. do a loader.render not active
913 pie_EnableFog(false); // don't let the normal loop code set status on
914 if (gameLoopStatus != GAMECODE_LOADGAME)
915 {
916 if (!levReleaseAll())
917 {
918 debug(LOG_ERROR, "levReleaseAll failed!");
919 }
920 for (auto& player : NetPlay.players)
921 {
922 player.resetAll();
923 }
924 }
925 closeLoadingScreen();
926 reloadMPConfig();
927 }
928
929 // Disable cursor trapping
930 if (war_GetTrapCursor())
931 {
932 wzReleaseMouse();
933 }
934
935 // Re-enable resizable windows
936 if (wzGetCurrentWindowMode() == WINDOW_MODE::windowed)
937 {
938 // FIXME: This is required because of the disabling in startGameLoop()
939 wzSetWindowIsResizable(true);
940 }
941
942 gameInitialised = false;
943 }
944
945
946 /*!
947 * Load a savegame and start into the game loop
948 * Game data should be initialised afterwards, so that startGameLoop is not necessary anymore.
949 */
initSaveGameLoad()950 static bool initSaveGameLoad()
951 {
952 // NOTE: always setGameMode correctly before *any* loading routines!
953 SetGameMode(GS_NORMAL);
954 initLoadingScreen(true);
955
956 // load up a save game
957 if (!loadGameInit(saveGameName))
958 {
959 // FIXME: we really should throw up a error window, but we can't (easily) so I won't.
960 debug(LOG_ERROR, "Trying to load Game %s failed!", saveGameName);
961 debug(LOG_POPUP, "Failed to load a save game! It is either corrupted or a unsupported format.\n\nRestarting main menu.");
962 // FIXME: If we bomb out on a in game load, then we would crash if we don't do the next two calls
963 // Doesn't seem to be a way to tell where we are in game loop to determine if/when we should do the two calls.
964 gameLoopStatus = GAMECODE_FASTEXIT; // clear out all old data
965 stopGameLoop();
966 startTitleLoop(); // Restart into titleloop
967 SetGameMode(GS_TITLE_SCREEN);
968 return false;
969 }
970
971 ActivityManager::instance().startingSavedGame();
972
973 // set up CD audio for game mode
974 // (must come after savegame is loaded so that proper game mode can be determined)
975 setCDAudioForCurrentGameMode();
976
977 screen_StopBackDrop();
978 closeLoadingScreen();
979
980 // Trap the cursor if cursor snapping is enabled
981 if (war_GetTrapCursor())
982 {
983 wzGrabMouse();
984 }
985 if (challengeActive)
986 {
987 addMissionTimerInterface();
988 }
989
990 return true;
991 }
992
993
994 /*!
995 * Run the code inside the gameloop
996 */
runGameLoop()997 static void runGameLoop()
998 {
999 gameLoopStatus = gameLoop();
1000 switch (gameLoopStatus)
1001 {
1002 case GAMECODE_CONTINUE:
1003 case GAMECODE_PLAYVIDEO:
1004 break;
1005 case GAMECODE_QUITGAME:
1006 debug(LOG_MAIN, "GAMECODE_QUITGAME");
1007 ActivityManager::instance().quitGame(collectEndGameStatsData(), Cheated);
1008 cdAudio_SetGameMode(MusicGameMode::MENUS);
1009 stopGameLoop();
1010 startTitleLoop(); // Restart into titleloop
1011 break;
1012 case GAMECODE_LOADGAME:
1013 debug(LOG_MAIN, "GAMECODE_LOADGAME");
1014 stopGameLoop();
1015 initSaveGameLoad(); // Restart and load a savegame
1016 break;
1017 case GAMECODE_NEWLEVEL:
1018 debug(LOG_MAIN, "GAMECODE_NEWLEVEL");
1019 stopGameLoop();
1020 startGameLoop(); // Restart gameloop
1021 break;
1022 // Never thrown:
1023 case GAMECODE_FASTEXIT:
1024 case GAMECODE_RESTARTGAME:
1025 break;
1026 default:
1027 debug(LOG_ERROR, "Unknown code returned by gameLoop");
1028 break;
1029 }
1030 }
1031
1032
1033 /*!
1034 * Run the code inside the titleloop
1035 */
runTitleLoop()1036 static void runTitleLoop()
1037 {
1038 switch (titleLoop())
1039 {
1040 case TITLECODE_CONTINUE:
1041 break;
1042 case TITLECODE_QUITGAME:
1043 debug(LOG_MAIN, "TITLECODE_QUITGAME");
1044 stopTitleLoop();
1045 wzQuit();
1046 break;
1047 case TITLECODE_SAVEGAMELOAD:
1048 {
1049 debug(LOG_MAIN, "TITLECODE_SAVEGAMELOAD");
1050 initLoadingScreen(true);
1051 // Restart into gameloop and load a savegame, ONLY on a good savegame load!
1052 stopTitleLoop();
1053 if (!initSaveGameLoad())
1054 {
1055 // we had a error loading savegame (corrupt?), so go back to title screen?
1056 stopGameLoop();
1057 startTitleLoop();
1058 changeTitleMode(TITLE);
1059 }
1060 closeLoadingScreen();
1061 break;
1062 }
1063 case TITLECODE_STARTGAME:
1064 debug(LOG_MAIN, "TITLECODE_STARTGAME");
1065 initLoadingScreen(true);
1066 stopTitleLoop();
1067 startGameLoop(); // Restart into gameloop
1068 closeLoadingScreen();
1069 break;
1070 case TITLECODE_SHOWINTRO:
1071 debug(LOG_MAIN, "TITLECODE_SHOWINTRO");
1072 seq_ClearSeqList();
1073 seq_AddSeqToList("titles.ogg", nullptr, nullptr, false);
1074 seq_AddSeqToList("devastation.ogg", nullptr, "devastation.txa", false);
1075 seq_StartNextFullScreenVideo();
1076 break;
1077 default:
1078 debug(LOG_ERROR, "Unknown code returned by titleLoop");
1079 break;
1080 }
1081 }
1082
1083 /*!
1084 * The mainloop.
1085 * Fetches events, executes appropriate code
1086 */
mainLoop()1087 void mainLoop()
1088 {
1089 frameUpdate(); // General housekeeping
1090
1091 // Screenshot key is now available globally
1092 if (keyPressed(KEY_F10))
1093 {
1094 kf_ScreenDump();
1095 inputLoseFocus(); // remove it from input stream
1096 }
1097
1098 wzSetCursor(CURSOR_DEFAULT); // if cursor isn't set by anything in the mainLoop, it should revert to default.
1099
1100 if (NetPlay.bComms || focusState == FOCUS_IN || !war_GetPauseOnFocusLoss())
1101 {
1102 if (loop_GetVideoStatus())
1103 {
1104 videoLoop(); // Display the video if necessary
1105 }
1106 else switch (GetGameMode())
1107 {
1108 case GS_NORMAL: // Run the gameloop code
1109 runGameLoop();
1110 break;
1111 case GS_TITLE_SCREEN: // Run the titleloop code
1112 runTitleLoop();
1113 break;
1114 default:
1115 break;
1116 }
1117 realTimeUpdate(); // Update realTime.
1118 }
1119
1120 wzApplyCursor();
1121 runNotifications();
1122 #if defined(ENABLE_DISCORD)
1123 discordRPCPerFrame();
1124 #endif
1125 }
1126
getUTF8CmdLine(int * const utfargc WZ_DECL_UNUSED,char *** const utfargv WZ_DECL_UNUSED)1127 bool getUTF8CmdLine(int *const utfargc WZ_DECL_UNUSED, char *** const utfargv WZ_DECL_UNUSED) // explicitely pass by reference
1128 {
1129 #ifdef WZ_OS_WIN
1130 int wargc;
1131 wchar_t **wargv = CommandLineToArgvW(GetCommandLineW(), &wargc);
1132
1133 if (wargv == NULL)
1134 {
1135 const int err = GetLastError();
1136 char *err_string;
1137
1138 // Retrieve a (locally encoded) string describing the error (uses LocalAlloc() to allocate memory)
1139 FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, err, 0, (char *)&err_string, 0, NULL);
1140
1141 debug(LOG_FATAL, "CommandLineToArgvW failed: %s (code:%d)", err_string, err);
1142
1143 LocalFree(err_string); // Free the chunk of memory FormatMessageA gave us
1144 LocalFree(wargv);
1145 return false;
1146 }
1147 // the following malloc and UTF16toUTF8 will be cleaned up in realmain().
1148 *utfargv = (char **)malloc(sizeof(char *) * wargc);
1149 if (!*utfargv)
1150 {
1151 debug(LOG_FATAL, "Out of memory!");
1152 abort();
1153 return false;
1154 }
1155 for (int i = 0; i < wargc; ++i)
1156 {
1157 STATIC_ASSERT(sizeof(wchar_t) == sizeof(utf_16_char)); // Should be true on windows
1158 (*utfargv)[i] = UTF16toUTF8((const utf_16_char *)wargv[i], NULL); // only returns null when memory runs out
1159 if ((*utfargv)[i] == NULL)
1160 {
1161 *utfargc = i;
1162 LocalFree(wargv);
1163 abort();
1164 return false;
1165 }
1166 }
1167 *utfargc = wargc;
1168 LocalFree(wargv);
1169 #endif
1170 return true;
1171 }
1172
1173 #if defined(WZ_OS_WIN)
1174
1175 #include <ntverp.h> // Windows SDK - include for access to VER_PRODUCTBUILD
1176 #if VER_PRODUCTBUILD >= 9200
1177 // 9200 is the Windows SDK 8.0 (which introduced family support)
1178 #include <winapifamily.h> // Windows SDK
1179 #else
1180 // Earlier SDKs don't have the concept of families - provide simple implementation
1181 // that treats everything as "desktop"
1182 #define WINAPI_PARTITION_DESKTOP 0x00000001
1183 #define WINAPI_FAMILY_PARTITION(Partition) ((WINAPI_PARTITION_DESKTOP & Partition) == Partition)
1184 #endif
1185
1186 typedef BOOL (WINAPI *SetDefaultDllDirectoriesFunction)(
1187 DWORD DirectoryFlags
1188 );
1189 #if !defined(LOAD_LIBRARY_SEARCH_APPLICATION_DIR)
1190 # define LOAD_LIBRARY_SEARCH_APPLICATION_DIR 0x00000200
1191 #endif
1192 #if !defined(LOAD_LIBRARY_SEARCH_DEFAULT_DIRS)
1193 # define LOAD_LIBRARY_SEARCH_DEFAULT_DIRS 0x00001000
1194 #endif
1195 #if !defined(LOAD_LIBRARY_SEARCH_SYSTEM32)
1196 # define LOAD_LIBRARY_SEARCH_SYSTEM32 0x00000800
1197 #endif
1198
1199 typedef BOOL (WINAPI *SetDllDirectoryWFunction)(
1200 LPCWSTR lpPathName
1201 );
1202
1203 typedef BOOL (WINAPI *SetSearchPathModeFunction)(
1204 DWORD Flags
1205 );
1206 #if !defined(BASE_SEARCH_PATH_ENABLE_SAFE_SEARCHMODE)
1207 # define BASE_SEARCH_PATH_ENABLE_SAFE_SEARCHMODE 0x00000001
1208 #endif
1209 #if !defined(BASE_SEARCH_PATH_PERMANENT)
1210 # define BASE_SEARCH_PATH_PERMANENT 0x00008000
1211 #endif
1212
1213 typedef BOOL (WINAPI *SetProcessDEPPolicyFunction)(
1214 DWORD dwFlags
1215 );
1216 #if !defined(PROCESS_DEP_ENABLE)
1217 # define PROCESS_DEP_ENABLE 0x00000001
1218 #endif
1219
osSpecificFirstChanceProcessSetup_Win()1220 void osSpecificFirstChanceProcessSetup_Win()
1221 {
1222 #if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
1223 HMODULE hKernel32 = GetModuleHandleW(L"kernel32");
1224
1225 SetDefaultDllDirectoriesFunction _SetDefaultDllDirectories = reinterpret_cast<SetDefaultDllDirectoriesFunction>(reinterpret_cast<void*>(GetProcAddress(hKernel32, "SetDefaultDllDirectories")));
1226
1227 if (_SetDefaultDllDirectories)
1228 {
1229 // Use SetDefaultDllDirectories to limit directories to search
1230 _SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_APPLICATION_DIR | LOAD_LIBRARY_SEARCH_SYSTEM32);
1231 }
1232
1233 SetDllDirectoryWFunction _SetDllDirectoryW = reinterpret_cast<SetDllDirectoryWFunction>(reinterpret_cast<void*>(GetProcAddress(hKernel32, "SetDllDirectoryW")));
1234
1235 if (_SetDllDirectoryW)
1236 {
1237 // Remove the current working directory from the default DLL search order
1238 _SetDllDirectoryW(L"");
1239 }
1240
1241 SetSearchPathModeFunction _SetSearchPathMode = reinterpret_cast<SetSearchPathModeFunction>(reinterpret_cast<void*>(GetProcAddress(hKernel32, "SetSearchPathMode")));
1242 if (_SetSearchPathMode)
1243 {
1244 // Enable safe search mode for the process
1245 _SetSearchPathMode(BASE_SEARCH_PATH_ENABLE_SAFE_SEARCHMODE | BASE_SEARCH_PATH_PERMANENT);
1246 }
1247 #endif /* WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) */
1248
1249 // Enable heap terminate-on-corruption.
1250 // A correct application can continue to run even if this call fails,
1251 // so it is safe to ignore the return value and call the function as follows:
1252 // (void)HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0);
1253 //
1254 #if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
1255 typedef BOOL (WINAPI *HSI)
1256 (HANDLE, HEAP_INFORMATION_CLASS, PVOID, SIZE_T);
1257 HSI pHsi = reinterpret_cast<HSI>(reinterpret_cast<void*>(GetProcAddress(hKernel32, "HeapSetInformation")));
1258 if (pHsi)
1259 {
1260 #ifndef HeapEnableTerminationOnCorruption
1261 # define HeapEnableTerminationOnCorruption (HEAP_INFORMATION_CLASS)1
1262 #endif
1263
1264 (void)((pHsi)(NULL, HeapEnableTerminationOnCorruption, NULL, 0));
1265 }
1266 #else
1267 (void)HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0);
1268 #endif /* WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) */
1269
1270 #if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
1271 SetProcessDEPPolicyFunction _SetProcessDEPPolicy = reinterpret_cast<SetProcessDEPPolicyFunction>(reinterpret_cast<void*>(GetProcAddress(hKernel32, "SetProcessDEPPolicy")));
1272 if (_SetProcessDEPPolicy)
1273 {
1274 // Ensure DEP is enabled
1275 _SetProcessDEPPolicy(PROCESS_DEP_ENABLE);
1276 }
1277 #endif /* WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) */
1278 }
1279 #endif /* defined(WZ_OS_WIN) */
1280
osSpecificFirstChanceProcessSetup()1281 void osSpecificFirstChanceProcessSetup()
1282 {
1283 #if defined(WZ_OS_WIN)
1284 osSpecificFirstChanceProcessSetup_Win();
1285 #elif defined(WZ_OS_UNIX)
1286 // Before anything else is run, and before creating any threads, ignore SIGPIPE
1287 // see: https://curl.haxx.se/libcurl/c/CURLOPT_NOSIGNAL.html
1288 struct sigaction sa;
1289 sigemptyset(&sa.sa_mask);
1290 sa.sa_handler = SIG_IGN;
1291 sa.sa_flags = 0;
1292 ignoredSIGPIPE = sigaction(SIGPIPE, &sa, 0) == 0;
1293
1294 // Initialize time conversion information
1295 tzset();
1296 #else
1297 // currently, no-op
1298 #endif
1299 }
1300
1301 // for backend detection
1302 extern const char *BACKEND;
1303
realmain(int argc,char * argv[])1304 int realmain(int argc, char *argv[])
1305 {
1306 int utfargc = argc;
1307 char **utfargv = (char **)argv;
1308
1309 osSpecificFirstChanceProcessSetup();
1310
1311 debug_init();
1312 debug_register_callback(debug_callback_stderr, nullptr, nullptr, nullptr);
1313 #if defined(WZ_OS_WIN) && defined(DEBUG_INSANE)
1314 debug_register_callback(debug_callback_win32debug, NULL, NULL, NULL);
1315 #endif // WZ_OS_WIN && DEBUG_INSANE
1316
1317 // *****
1318 // NOTE: Try *NOT* to use debug() output routines without some other method of informing the user. All this output is sent to /dev/nul at this point on some platforms!
1319 // *****
1320 if (!getUTF8CmdLine(&utfargc, &utfargv))
1321 {
1322 return EXIT_FAILURE;
1323 }
1324
1325 /*** Initialize PhysicsFS ***/
1326 initialize_PhysicsFS(utfargv[0]);
1327
1328 /*** Initialize translations ***/
1329 /*** NOTE: Should occur before any use of gettext / libintl translation routines. ***/
1330 initI18n();
1331
1332 wzMain(argc, argv); // init Qt integration first
1333
1334 LaunchInfo::initialize(argc, argv);
1335 setupExceptionHandler(utfargc, utfargv, version_getFormattedVersionString(false), version_getVersionedAppDirFolderName(), isPortableMode());
1336
1337 /*** Initialize sodium library ***/
1338 if (sodium_init() < 0) {
1339 /* libsodium couldn't be initialized - it is not safe to use */
1340 fprintf(stderr, "Failed to initialize libsodium\n");
1341 return EXIT_FAILURE;
1342 }
1343
1344 /*** Initialize URL Request library ***/
1345 urlRequestInit();
1346
1347 // find early boot info
1348 if (!ParseCommandLineEarly(utfargc, utfargv))
1349 {
1350 return EXIT_FAILURE;
1351 }
1352
1353 /* Initialize the write/config directory for PhysicsFS.
1354 * This needs to be done __after__ the early commandline parsing,
1355 * because the user might tell us to use an alternative configuration
1356 * directory.
1357 */
1358 initialize_ConfigDir();
1359
1360 /*** Initialize directory structure ***/
1361
1362 PHYSFS_mkdir("autohost"); // autohost games launched with --autohost=game
1363
1364 PHYSFS_mkdir("challenges"); // custom challenges
1365
1366 PHYSFS_mkdir("logs"); // netplay, mingw crash reports & WZ logs
1367
1368 make_dir(MultiCustomMapsPath, "maps", nullptr); // needed to prevent crashes when getting map
1369
1370 PHYSFS_mkdir("mods/autoload"); // mods that are automatically loaded
1371 PHYSFS_mkdir("mods/campaign"); // campaign only mods activated with --mod_ca=example.wz
1372 PHYSFS_mkdir("mods/downloads"); // mod download directory
1373 PHYSFS_mkdir("mods/global"); // global mods activated with --mod=example.wz
1374 PHYSFS_mkdir("mods/multiplay"); // multiplay only mods activated with --mod_mp=example.wz
1375 PHYSFS_mkdir("mods/music"); // music mods that are automatically loaded
1376
1377 make_dir(MultiPlayersPath, "multiplay", "players"); // player profiles
1378
1379 PHYSFS_mkdir("music"); // custom music overriding default music and music mods
1380
1381 make_dir(SaveGamePath, "savegames", nullptr); // save games
1382 PHYSFS_mkdir("savegames/campaign"); // campaign save games
1383 PHYSFS_mkdir("savegames/campaign/auto"); // campaign autosave games
1384 PHYSFS_mkdir("savegames/skirmish"); // skirmish save games
1385 PHYSFS_mkdir("savegames/skirmish/auto"); // skirmish autosave games
1386
1387 make_dir(ScreenDumpPath, "screenshots", nullptr); // for screenshots
1388
1389 PHYSFS_mkdir("tests"); // test games launched with --skirmish=game
1390
1391 PHYSFS_mkdir("userdata"); // per-mod data user generated data
1392 PHYSFS_mkdir("userdata/campaign"); // contains campaign templates
1393 PHYSFS_mkdir("userdata/mp"); // contains multiplayer templates
1394 memset(rulesettag, 0, sizeof(rulesettag)); // stores tag property of ruleset.json files
1395
1396 if (!customDebugfile)
1397 {
1398 // there was no custom debug file specified (--debug-file=blah)
1399 // so we use our write directory to store our logs.
1400 time_t aclock;
1401 struct tm newtime;
1402 char buf[PATH_MAX];
1403
1404 time(&aclock); // Get time in seconds
1405 newtime = getLocalTime(aclock); // Convert time to struct
1406 // Note: We are using fopen(), and not physfs routines to open the file
1407 // log name is logs/(or \)WZlog-MMDD_HHMMSS.txt
1408 snprintf(buf, sizeof(buf), "%slogs%sWZlog-%02d%02d_%02d%02d%02d.txt", PHYSFS_getWriteDir(), PHYSFS_getDirSeparator(),
1409 newtime.tm_mon + 1, newtime.tm_mday, newtime.tm_hour, newtime.tm_min, newtime.tm_sec);
1410 WzString debug_filename = buf;
1411 debug_register_callback(debug_callback_file, debug_callback_file_init, debug_callback_file_exit, &debug_filename); // note: by the time this function returns, all use of debug_filename has completed
1412
1413 debug(LOG_WZ, "Using %s debug file", buf);
1414 }
1415
1416 // Initialize random number generators
1417 srand((unsigned int)time(NULL));
1418 gameSRand((uint32_t)rand());
1419
1420 // NOTE: it is now safe to use debug() calls to make sure output gets captured.
1421 check_Physfs();
1422 debug(LOG_WZ, "Warzone 2100 - %s", version_getFormattedVersionString(false));
1423 debug(LOG_WZ, "Using language: %s", getLanguage());
1424 debug(LOG_WZ, "Backend: %s", BACKEND);
1425 debug(LOG_MEMORY, "sizeof: SIMPLE_OBJECT=%ld, BASE_OBJECT=%ld, DROID=%ld, STRUCTURE=%ld, FEATURE=%ld, PROJECTILE=%ld",
1426 (long)sizeof(SIMPLE_OBJECT), (long)sizeof(BASE_OBJECT), (long)sizeof(DROID), (long)sizeof(STRUCTURE), (long)sizeof(FEATURE), (long)sizeof(PROJECTILE));
1427
1428 #if defined(WZ_OS_UNIX)
1429 debug(LOG_WZ, "Ignoring SIGPIPE: %s", (ignoredSIGPIPE) ? "true" : "false");
1430 #endif
1431 urlRequestOutputDebugInfo();
1432
1433 /* Put in the writedir root */
1434 sstrcpy(KeyMapPath, "keymap.json");
1435
1436 // initialise all the command line states
1437 war_SetDefaultStates();
1438
1439 debug(LOG_MAIN, "initializing");
1440
1441 loadConfig();
1442
1443 // parse the command line
1444 if (!ParseCommandLine(utfargc, utfargv))
1445 {
1446 return EXIT_FAILURE;
1447 }
1448
1449 // Save new (commandline) settings
1450 saveConfig();
1451
1452 // Print out some initial information if in headless mode
1453 if (headlessGameMode())
1454 {
1455 fprintf(stdout, "--------------------------------------------------------------------------------------\n");
1456 fprintf(stdout, " * Warzone 2100 - Headless Mode\n");
1457 fprintf(stdout, " * %s\n", version_getFormattedVersionString(false));
1458 if (to_swap_mode(war_GetVsync()) == gfx_api::context::swap_interval_mode::immediate)
1459 {
1460 fprintf(stdout, " * NOTE: VSYNC IS DISABLED - CPU USAGE MAY BE UNBOUNDED\n");
1461 }
1462 fprintf(stdout, "--------------------------------------------------------------------------------------\n");
1463 fflush(stdout);
1464 }
1465
1466 // Find out where to find the data
1467 scanDataDirs();
1468
1469 // Now we check the mods to see if they exist or not (specified on the command line)
1470 // FIX ME: I know this is a bit hackish, but better than nothing for now?
1471 {
1472 char modtocheck[256];
1473 #if defined WZ_PHYSFS_2_1_OR_GREATER
1474 PHYSFS_Stat metaData;
1475 #endif
1476
1477 // check whether given global mods are regular files
1478 for (auto iterator = global_mods.begin(); iterator != global_mods.end();)
1479 {
1480 ssprintf(modtocheck, "mods/global/%s", iterator->c_str());
1481 #if defined WZ_PHYSFS_2_0_OR_GREATER
1482 if (!PHYSFS_exists(modtocheck) || WZ_PHYSFS_isDirectory(modtocheck))
1483 #elif defined WZ_PHYSFS_2_1_OR_GREATER
1484 PHYSFS_stat(modtocheck, &metaData);
1485 if (metaData.filetype != PHYSFS_FILETYPE_REGULAR)
1486 #endif
1487 {
1488 debug(LOG_ERROR, "The global mod \"%s\" you have specified doesn't exist!", iterator->c_str());
1489 global_mods.erase(iterator);
1490 rebuildSearchPath(mod_multiplay, true);
1491 }
1492 else
1493 {
1494 wz_info("global mod \"%s\" is enabled", iterator->c_str());
1495 ++iterator;
1496 }
1497 }
1498 // check whether given campaign mods are regular files
1499 for (auto iterator = campaign_mods.begin(); iterator != campaign_mods.end();)
1500 {
1501 ssprintf(modtocheck, "mods/campaign/%s", iterator->c_str());
1502 #if defined WZ_PHYSFS_2_0_OR_GREATER
1503 if (!PHYSFS_exists(modtocheck) || WZ_PHYSFS_isDirectory(modtocheck))
1504 #elif defined WZ_PHYSFS_2_1_OR_GREATER
1505 PHYSFS_stat(modtocheck, &metaData);
1506 if (metaData.filetype != PHYSFS_FILETYPE_REGULAR)
1507 #endif
1508 {
1509 debug(LOG_ERROR, "The campaign mod \"%s\" you have specified doesn't exist!", iterator->c_str());
1510 campaign_mods.erase(iterator);
1511 rebuildSearchPath(mod_campaign, true);
1512 }
1513 else
1514 {
1515 wz_info("campaign mod \"%s\" is enabled", iterator->c_str());
1516 ++iterator;
1517 }
1518 }
1519 // check whether given multiplay mods are regular files
1520 for (auto iterator = multiplay_mods.begin(); iterator != multiplay_mods.end();)
1521 {
1522 ssprintf(modtocheck, "mods/multiplay/%s", iterator->c_str());
1523 #if defined WZ_PHYSFS_2_0_OR_GREATER
1524 if (!PHYSFS_exists(modtocheck) || WZ_PHYSFS_isDirectory(modtocheck))
1525 #elif defined WZ_PHYSFS_2_1_OR_GREATER
1526 PHYSFS_stat(modtocheck, &metaData);
1527 if (metaData.filetype != PHYSFS_FILETYPE_REGULAR)
1528 #endif
1529 {
1530 debug(LOG_ERROR, "The multiplay mod \"%s\" you have specified doesn't exist!", iterator->c_str());
1531 multiplay_mods.erase(iterator);
1532 rebuildSearchPath(mod_multiplay, true);
1533 }
1534 else
1535 {
1536 wz_info("multiplay mod \"%s\" is enabled", iterator->c_str());
1537 ++iterator;
1538 }
1539 }
1540 }
1541
1542 ActivityManager::instance().initialize();
1543
1544 optional<video_backend> gfxbackend;
1545 if (!headlessGameMode())
1546 {
1547 gfxbackend = war_getGfxBackend();
1548 }
1549 if (!wzMainScreenSetup(gfxbackend, war_getAntialiasing(), war_getWindowMode(), war_GetVsync()))
1550 {
1551 saveConfig(); // ensure any setting changes are persisted on failure
1552 return EXIT_FAILURE;
1553 }
1554
1555 debug(LOG_WZ, "Warzone 2100 - %s", version_getFormattedVersionString(false));
1556 debug(LOG_WZ, "Using language: %s", getLanguage());
1557 debug(LOG_WZ, "Backend: %s", BACKEND);
1558 debug(LOG_MEMORY, "sizeof: SIMPLE_OBJECT=%ld, BASE_OBJECT=%ld, DROID=%ld, STRUCTURE=%ld, FEATURE=%ld, PROJECTILE=%ld",
1559 (long)sizeof(SIMPLE_OBJECT), (long)sizeof(BASE_OBJECT), (long)sizeof(DROID), (long)sizeof(STRUCTURE), (long)sizeof(FEATURE), (long)sizeof(PROJECTILE));
1560
1561 int w = pie_GetVideoBufferWidth();
1562 int h = pie_GetVideoBufferHeight();
1563
1564 char buf[256];
1565 ssprintf(buf, "Video Mode %d x %d (%s)", w, h, to_display_string(war_getWindowMode()).c_str());
1566 addDumpInfo(buf);
1567
1568 float horizScaleFactor, vertScaleFactor;
1569 wzGetGameToRendererScaleFactor(&horizScaleFactor, &vertScaleFactor);
1570 debug(LOG_WZ, "Game to Renderer Scale Factor (w x h): %f x %f", horizScaleFactor, vertScaleFactor);
1571
1572 debug(LOG_MAIN, "Final initialization");
1573 if (!frameInitialise())
1574 {
1575 return EXIT_FAILURE;
1576 }
1577 if (!screenInitialise())
1578 {
1579 return EXIT_FAILURE;
1580 }
1581 if (!pie_LoadShaders())
1582 {
1583 return EXIT_FAILURE;
1584 }
1585
1586 if (!headlessGameMode())
1587 {
1588 unsigned int windowWidth = 0, windowHeight = 0;
1589 wzGetWindowResolution(nullptr, &windowWidth, &windowHeight);
1590 war_SetWidth(windowWidth);
1591 war_SetHeight(windowHeight);
1592 }
1593
1594 pie_SetFogStatus(false);
1595 pie_ScreenFlip(CLEAR_BLACK);
1596
1597 pal_Init();
1598
1599 pie_LoadBackDrop(SCREEN_RANDOMBDROP);
1600 pie_SetFogStatus(false);
1601 pie_ScreenFlip(CLEAR_BLACK);
1602
1603 if (!systemInitialise(horizScaleFactor, vertScaleFactor))
1604 {
1605 return EXIT_FAILURE;
1606 }
1607
1608 //set all the pause states to false
1609 setAllPauseStates(false);
1610
1611 // Copy this info to be used by the crash handler for the dump file
1612 ssprintf(buf, "Using Backend: %s", BACKEND);
1613 addDumpInfo(buf);
1614 ssprintf(buf, "Using language: %s", getLanguageName());
1615 addDumpInfo(buf);
1616
1617 // Do the game mode specific initialisation.
1618 switch (GetGameMode())
1619 {
1620 case GS_TITLE_SCREEN:
1621 startTitleLoop();
1622 break;
1623 case GS_SAVEGAMELOAD:
1624 if (headlessGameMode())
1625 {
1626 fprintf(stdout, "Loading savegame ...\n");
1627 }
1628 initSaveGameLoad();
1629 break;
1630 case GS_NORMAL:
1631 startGameLoop();
1632 break;
1633 default:
1634 debug(LOG_ERROR, "Weirdy game status, I'm afraid!!");
1635 break;
1636 }
1637
1638 WzInfoManager::initialize();
1639 #if defined(ENABLE_DISCORD)
1640 discordRPCInitialize();
1641 #endif
1642
1643 #if defined(WZ_CC_MSVC) && defined(DEBUG)
1644 debug_MEMSTATS();
1645 #endif
1646 debug(LOG_MAIN, "Entering main loop");
1647
1648 #if defined(WZ_OS_MAC)
1649 if (headlessGameMode())
1650 {
1651 cocoaTransformToBackgroundApplication();
1652 }
1653 #endif
1654
1655 wzMainEventLoop();
1656 ActivityManager::instance().preSystemShutdown();
1657
1658 switch (GetGameMode())
1659 {
1660 case GS_NORMAL:
1661 // if running a game while quitting, stop the game loop
1662 // (currently required for some cleanup) (should modelShutdown() be added to systemShutdown?)
1663 stopGameLoop();
1664 break;
1665 case GS_TITLE_SCREEN:
1666 // if showing the title / menus while quitting, stop the title loop
1667 // (currently required for some cleanup)
1668 stopTitleLoop();
1669 break;
1670 default:
1671 break;
1672 }
1673 saveConfig();
1674 #if defined(ENABLE_DISCORD)
1675 discordRPCShutdown();
1676 #endif
1677 systemShutdown();
1678 #ifdef WZ_OS_WIN // clean up the memory allocated for the command line conversion
1679 for (int i = 0; i < argc; i++)
1680 {
1681 char *** const utfargvF = &utfargv;
1682 free((void *)(*utfargvF)[i]);
1683 }
1684 free(utfargv);
1685 #endif
1686 ActivityManager::instance().shutdown();
1687 wzShutdown();
1688 urlRequestShutdown();
1689 debug(LOG_MAIN, "Completed shutting down Warzone 2100");
1690 return EXIT_SUCCESS;
1691 }
1692
1693 /*!
1694 * Get the mode the game is currently in
1695 */
GetGameMode()1696 GS_GAMEMODE GetGameMode()
1697 {
1698 return gameStatus;
1699 }
1700
1701 /*!
1702 * Set the current mode
1703 */
SetGameMode(GS_GAMEMODE status)1704 void SetGameMode(GS_GAMEMODE status)
1705 {
1706 gameStatus = status;
1707 }
1708