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