1 /*
2  *  utils.cc - Common utility routines.
3  *
4  *  Copyright (C) 1998-1999  Jeffrey S. Freedman
5  *  Copyright (C) 2000-2013  The Exult Team
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation; either version 2 of the License, or
10  *  (at your option) any later version.
11  *
12  *  This program is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with this program; if not, write to the Free Software
19  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20  */
21 
22 #ifdef HAVE_CONFIG_H
23 #  include <config.h>
24 #endif
25 
26 #include <cctype>
27 #include <cstdio>
28 #include <cstdlib>
29 #include <cstring>
30 #include <string>
31 #include <fstream>
32 #include <map>
33 #include <vector>
34 #include <list>
35 #include <sys/stat.h>
36 #include <unistd.h>
37 
38 #ifdef __IPHONEOS__
39 #  include "ios_utils.h"
40 #endif
41 
42 #ifdef _WIN32
43 #include <windows.h>
44 #include <shlobj.h>
45 #include <direct.h> // For mkdir and chdir
46 #endif
47 
48 #include <cassert>
49 #include <exception>
50 #include "exceptions.h"
51 #include "utils.h"
52 #include "fnames.h"
53 #include "ignore_unused_variable_warning.h"
54 
55 #if defined(MACOSX) || defined(__IPHONEOS__)
56 #  include <CoreFoundation/CoreFoundation.h>
57 #  include <sys/param.h> // for MAXPATHLEN
58 #endif
59 
60 using std::cerr;
61 using std::string;
62 using std::ios;
63 
64 // Function prototypes
65 
66 static void switch_slashes(string &name);
67 static bool base_to_uppercase(string &str, int count);
68 
69 
70 // Wrap a few functions
stat(const std::string & file_name,struct stat * buf)71 inline int stat(const std::string &file_name, struct stat *buf) {
72 	return stat(file_name.c_str(), buf);
73 }
74 
75 // Ugly hack for supporting different paths
76 
77 static std::map<string, string> path_map;
78 static std::map<string, string> stored_path_map;
79 
store_system_paths()80 void store_system_paths() {
81 	stored_path_map = path_map;
82 }
83 
reset_system_paths()84 void reset_system_paths() {
85 	path_map = stored_path_map;
86 }
87 
is_path_separator(char cc)88 static bool is_path_separator(char cc) {
89 #ifdef _WIN32
90 	return cc == '/' || cc == '\\';
91 #else
92 	return cc == '/';
93 #endif
94 }
95 
remove_trailing_slash(const string & value)96 static string remove_trailing_slash(const string &value) {
97 	string new_path = value;
98 	if (is_path_separator(new_path.back())) {
99 		std::cerr << "Warning, trailing slash in path: \"" << new_path << "\"" << std::endl;
100 		new_path.resize(new_path.size() - 1);
101 	}
102 
103 	return new_path;
104 }
105 
add_system_path(const string & key,const string & value)106 void add_system_path(const string &key, const string &value) {
107 	if (!value.empty()) {
108 		if (value.find(key) != string::npos) {
109 			std::cerr << "Error: system path '" << key
110 			          << "' is being defined in terms of itself: '"
111 			          << value << "'." << std::endl;
112 			exit(1);
113 		} else
114 			path_map[key] = remove_trailing_slash(value);
115 	} else {
116 		clear_system_path(key);
117 	}
118 }
119 
clone_system_path(const string & new_key,const string & old_key)120 void clone_system_path(const string &new_key, const string &old_key) {
121 	if (is_system_path_defined(old_key)) {
122 		path_map[new_key] = path_map[old_key];
123 	} else {
124 		clear_system_path(new_key);
125 	}
126 }
127 
clear_system_path(const string & key)128 void clear_system_path(const string &key) {
129 	auto iter = path_map.find(key);
130 	if (iter != path_map.end())
131 		path_map.erase(iter);
132 }
133 
134 /*
135  *  Has a path been entered?
136  */
is_system_path_defined(const string & path)137 bool is_system_path_defined(const string &path) {
138 	return path_map.find(path) != path_map.end();
139 }
140 
141 /*
142  *  Convert an exult path (e.g. "<DATA>/exult.flx") into a system path
143  */
144 
get_system_path(const string & path)145 string get_system_path(const string &path) {
146 	string new_path = path;
147 	string::size_type pos;
148 	string::size_type pos2;
149 
150 	pos = new_path.find('>');
151 	pos2 = new_path.find('<');
152 	// If there is no separator, return the path as is
153 	int cnt = 10;
154 	while (pos != string::npos && pos2 == 0 && cnt-- > 0) {
155 		pos += 1;
156 		// See if we can translate this prefix
157 		string syspath = new_path.substr(0, pos);
158 		if (is_system_path_defined(syspath)) {
159 			string new_prefix = path_map[syspath];
160 			new_prefix += new_path.substr(pos);
161 			new_path.swap(new_prefix);
162 			pos = new_path.find('>');
163 			pos2 = new_path.find('<');
164 		} else {
165 #ifdef DEBUG
166 			std::cerr << "Unrecognized system path '" << syspath
167 			          << "' in path '" << path << "'." << std::endl;
168 #endif
169 			break;
170 		}
171 	}
172 	if (cnt <= 0) {
173 		std::cerr << "Could not convert path '" << path
174 		          << "' into a filesystem path, due to mutually recursive system paths." << std::endl;
175 		std::cerr << "Expansion resulted in '" << new_path << "'." << std::endl;
176 		exit(1);
177 	}
178 
179 	switch_slashes(new_path);
180 #ifdef _WIN32
181 	if (new_path.back() == '/' || new_path.back() == '\\') {
182 		//std::cerr << "Trailing slash in path: \"" << new_path << "\"" << std::endl << "...compensating, but go complain to Colourless anyway" << std::endl;
183 		std::cerr << "Warning, trailing slash in path: \"" << new_path << "\"" << std::endl;
184 		new_path += '.';
185 	}
186 #ifdef NO_WIN32_PATH_SPACES
187 	pos = new_path.find('*');
188 	pos2 = new_path.find('?');
189 	string::size_type pos3 = new_path.find(' ');
190 	// pos and pos2 will equal each other if neither is found
191 	// and we'll only convert to short if a space is found
192 	// really, we don't need to do this, but hey, you never know
193 	if (pos == pos2 && pos3 != string::npos) {
194 		int num_chars = GetShortPathName(new_path.c_str(), nullptr, 0);
195 		if (num_chars > 0) {
196 			std::string short_path(num_chars + 1);
197 			GetShortPathName(new_path.c_str(), &short_path[0], num_chars + 1);
198 			new_path = std::move(short_path);
199 		}
200 		//else std::cerr << "Warning unable to get short path for: " << new_path << std::endl;
201 	}
202 #endif
203 #endif
204 	return new_path;
205 }
206 
207 
208 /*
209  *  Convert a buffer to upper-case.
210  *
211  *  Output: ->original buffer, changed to upper case.
212  */
213 
to_uppercase(string & str)214 void to_uppercase(string &str) {
215 	for (auto& chr : str) {
216 		chr = static_cast<char>(std::toupper(static_cast<unsigned char>(chr)));
217 	}
218 }
219 
to_uppercase(const string & str)220 string to_uppercase(const string &str) {
221 	string s(str);
222 	to_uppercase(s);
223 	return s;
224 }
225 
226 /*
227  *  Convert just the last 'count' parts of a filename to uppercase.
228  *  returns false if there are less than 'count' parts
229  */
230 
base_to_uppercase(string & str,int count)231 static bool base_to_uppercase(string &str, int count) {
232 	if (count <= 0) return true;
233 
234 	int todo = count;
235 	// Go backwards.
236 	string::reverse_iterator X;
237 	for (X = str.rbegin(); X != str.rend(); ++X) {
238 		// Stop at separator.
239 		if (*X == '/' || *X == '\\' || *X == ':')
240 			todo--;
241 		if (todo <= 0)
242 			break;
243 
244 		*X = static_cast<char>(std::toupper(static_cast<unsigned char>(*X)));
245 	}
246 	if (X == str.rend())
247 		todo--; // start of pathname counts as separator too
248 
249 	// false if it didn't reach 'count' parts
250 	return todo <= 0;
251 }
252 
253 
254 
switch_slashes(string & name)255 static void switch_slashes(
256     string &name
257 ) {
258 #ifdef _WIN32
259 	for (char& X : name) {
260 		if (X == '/')
261 			X = '\\';
262 	}
263 #else
264 	ignore_unused_variable_warning(name);
265 	// do nothing
266 #endif
267 }
268 
269 /*
270  *  Open a file for input,
271  *  trying the original name (lower case), and the upper case version
272  *  of the name.
273  *
274  *  Output: 0 if couldn't open.
275  */
276 
U7open(std::ifstream & in,const char * fname,bool is_text)277 bool U7open(
278     std::ifstream &in,          // Input stream to open.
279     const char *fname,          // May be converted to upper-case.
280     bool is_text                // Should the file be opened in text mode
281 ) {
282 	std::ios_base::openmode mode = std::ios::in;
283 	if (!is_text) mode |= std::ios::binary;
284 	string name = get_system_path(fname);
285 	int uppercasecount = 0;
286 	do {
287 		// We first "clear" the stream object. This is done to prevent
288 		// problems when re-using stream objects
289 		in.clear();
290 		try {
291 			//std::cout << "trying: " << name << std::endl;
292 			in.open(name.c_str(), mode);        // Try to open
293 		} catch (std::exception &)
294 		{}
295 		if (in.good() && !in.fail()) {
296 			//std::cout << "got it!" << std::endl;
297 			return true; // found it!
298 		}
299 	} while (base_to_uppercase(name, ++uppercasecount));
300 
301 	// file not found.
302 	throw file_open_exception(get_system_path(fname));
303 	return false;
304 }
305 
306 /*
307  *  Open a file for output,
308  *  trying the original name (lower case), and the upper case version
309  *  of the name.
310  *
311  *  Output: 0 if couldn't open.
312  */
313 
U7open(std::ofstream & out,const char * fname,bool is_text)314 bool U7open(
315     std::ofstream &out,         // Output stream to open.
316     const char *fname,          // May be converted to upper-case.
317     bool is_text                // Should the file be opened in text mode
318 ) {
319 	std::ios_base::openmode mode = std::ios::out | std::ios::trunc;
320 	if (!is_text) mode |= std::ios::binary;
321 	string name = get_system_path(fname);
322 
323 	// We first "clear" the stream object. This is done to prevent
324 	// problems when re-using stream objects
325 	out.clear();
326 
327 	int uppercasecount = 0;
328 	do {
329 		out.open(name.c_str(), mode);       // Try to open
330 		if (out.good())
331 			return true; // found it!
332 		out.clear();    // Forget ye not
333 	} while (base_to_uppercase(name, ++uppercasecount));
334 
335 	// file not found.
336 	throw file_open_exception(get_system_path(fname));
337 	return false;
338 }
339 
U7opendir(const char * fname)340 DIR *U7opendir(
341     const char *fname			// May be converted to upper-case.
342 ) {
343 	string name = get_system_path(fname);
344 	int uppercasecount = 0;
345 
346 	do {
347 		DIR *dir = opendir(name.c_str()); // Try to open
348 		if (dir)
349 			return dir; // found it!
350 	} while (base_to_uppercase(name, ++uppercasecount));
351 	return nullptr;
352 }
353 
354 /*
355  *  Remove a file taking care of paths etc.
356  *
357  */
358 
U7remove(const char * fname)359 void U7remove(
360     const char *fname         // May be converted to upper-case.
361 ) {
362 	string name = get_system_path(fname);
363 
364 #if defined(_WIN32) && defined(UNICODE)
365 	const char *n = name.c_str();
366 	int nLen = std::strlen(n) + 1;
367 	LPTSTR lpszT = (LPTSTR) alloca(nLen * 2);
368 	MultiByteToWideChar(CP_ACP, 0, n, -1, lpszT, nLen);
369 	DeleteFile(lpszT);
370 #else
371 
372 	struct stat sbuf;
373 
374 	int uppercasecount = 0;
375 	do {
376 		bool exists = (stat(name, &sbuf) == 0);
377 		if (exists) {
378 			std::remove(name.c_str());
379 		}
380 	} while (base_to_uppercase(name, ++uppercasecount));
381 	std::remove(name.c_str());
382 #endif
383 }
384 
385 /*
386  *  Open a "static" game file by first looking in <PATCH>, then
387  *  <STATIC>.
388  *  Output: 0 if couldn't open. We do NOT throw exceptions.
389  */
390 
U7open_static(std::ifstream & in,const char * fname,bool is_text)391 bool U7open_static(
392     std::ifstream &in,      // Input stream to open.
393     const char *fname,      // May be converted to upper-case.
394     bool is_text            // Should file be opened in text mode
395 ) {
396 	string name;
397 
398 	name = string("<PATCH>/") + fname;
399 	try {
400 		if (U7open(in, name.c_str(), is_text))
401 			return true;
402 	} catch (std::exception &)
403 	{}
404 	name = string("<STATIC>/") + fname;
405 	try {
406 		if (U7open(in, name.c_str(), is_text))
407 			return true;
408 	} catch (std::exception &)
409 	{}
410 	return false;
411 }
412 
413 /*
414  *  See if a file exists.
415  */
416 
U7exists(const char * fname)417 bool U7exists(
418     const char *fname         // May be converted to upper-case.
419 ) {
420 	string name = get_system_path(fname);
421 	struct stat sbuf;
422 
423 	int uppercasecount = 0;
424 	do {
425 		bool exists = (stat(name, &sbuf) == 0);
426 		if (exists)
427 			return true; // found it!
428 	} while (base_to_uppercase(name, ++uppercasecount));
429 
430 	// file not found
431 	return false;
432 }
433 
434 /*
435  *  Create a directory
436  */
437 
U7mkdir(const char * dirname,int mode)438 int U7mkdir(
439     const char *dirname,    // May be converted to upper-case.
440     int mode
441 ) {
442 	string name = get_system_path(dirname);
443 	// remove any trailing slashes
444 	string::size_type pos = name.find_last_not_of('/');
445 	if (pos != string::npos)
446 		name.resize(pos + 1);
447 #if defined(_WIN32) && defined(UNICODE)
448 	const char *n = name.c_str();
449 	int nLen = std::strlen(n) + 1;
450 	LPTSTR lpszT = (LPTSTR) alloca(nLen * 2);
451 	MultiByteToWideChar(CP_ACP, 0, n, -1, lpszT, nLen);
452 	ignore_unused_variable_warning(mode);
453 	return CreateDirectory(lpszT, nullptr);
454 #elif defined(_WIN32)
455 	ignore_unused_variable_warning(mode);
456 	return mkdir(name.c_str());
457 #else
458 	return mkdir(name.c_str(), mode); // Create dir. if not already there.
459 #endif
460 }
461 
462 #ifdef _WIN32
463 class shell32_wrapper {
464 protected:
465 	HMODULE hLib;
466 	using SHGetFolderPathFunc = HRESULT (WINAPI*)(
467 	    HWND hwndOwner,
468 	    int nFolder,
469 	    HANDLE hToken,
470 	    DWORD dwFlags,
471 	    LPTSTR pszPath
472 	);
473 	SHGetFolderPathFunc      SHGetFolderPath;
474 	/*
475 	// Will leave this for someone with Vista/W7 to implement.
476 	using SHGetKnownFolderPathFunc = HRESULT (WINAPI*) (
477 	    REFKNOWNFOLDERID rfid,
478 	    DWORD dwFlags,
479 	    HANDLE hToken,
480 	    PWSTR *ppszPath
481     );
482 	SHGetKnownFolderPathFunc SHGetKnownFolderPath;
483 	*/
484 
485 	template <typename Dest>
get_function(const char * func)486 	Dest get_function(const char *func) {
487 		static_assert(sizeof(Dest) == sizeof(decltype(GetProcAddress(hLib, func))), "sizeof(FARPROC) is not equal to sizeof(Dest)!");
488 		Dest fptr;
489 		const FARPROC optr = GetProcAddress(hLib, func);
490 		std::memcpy(&fptr, &optr, sizeof(optr));
491 		return fptr;
492 	}
493 
494 public:
shell32_wrapper()495 	shell32_wrapper() {
496 		hLib = LoadLibrary("shell32.dll");
497 		if (hLib != nullptr) {
498 			SHGetFolderPath      = get_function<SHGetFolderPathFunc>("SHGetFolderPathA");
499 			/*
500 			SHGetKnownFolderPath = get_function<SHGetKnownFolderPathFunc>("SHGetKnownFolderPath");
501 			*/
502 		} else {
503 			SHGetFolderPath      = nullptr;
504 			//SHGetKnownFolderPath = nullptr;
505 		}
506 	}
~shell32_wrapper()507 	~shell32_wrapper() {
508 		FreeLibrary(hLib);
509 	}
Get_local_appdata()510 	string Get_local_appdata() {
511 		/*  Not yet.
512 		if (SHGetKnownFolderPath != nullptr)
513 		    {
514 		    }
515 		else */
516 		if (SHGetFolderPath != nullptr) {
517 			CHAR szPath[MAX_PATH];
518 			HRESULT code = SHGetFolderPath(nullptr, CSIDL_LOCAL_APPDATA,
519 			                               nullptr, 0, szPath);
520 			if (code == E_INVALIDARG)
521 				return string();
522 			else if (code == S_FALSE)   // E_FAIL for Unicode version.
523 				// Lets try creating it through the API flag:
524 				code = SHGetFolderPath(nullptr,
525 				                       CSIDL_LOCAL_APPDATA | CSIDL_FLAG_CREATE,
526 				                       nullptr, 0, szPath);
527 			if (code == E_INVALIDARG)
528 				return string();
529 			else if (code == S_OK)
530 				return string(reinterpret_cast<const char *>(szPath));
531 			// We don't have a folder yet at this point. This means we have
532 			// a truly ancient version of Windows.
533 			// Just to be sure, we fall back to the old behaviour.
534 			// Is anyone still needing this?
535 		}
536 		return string();
537 	}
538 };
539 
540 #ifdef USE_CONSOLE
redirect_output(const char * prefix)541 void redirect_output(const char *prefix) {
542 	ignore_unused_variable_warning(prefix);
543 }
cleanup_output(const char * prefix)544 void cleanup_output(const char *prefix) {
545 	ignore_unused_variable_warning(prefix);
546 }
547 #else
548 std::string Get_home();
549 
550 // Pulled from exult_studio.cc.
redirect_output(const char * prefix)551 void redirect_output(const char *prefix) {
552 	if (GetFileType(GetStdHandle(STD_OUTPUT_HANDLE)) == FILE_TYPE_PIPE) {
553 		// If we are at a msys/msys2 shell, do not redirect the output, and
554 		// print it to console instead.
555 		return;
556 	}
557 	// Starting from GUI, or from cmd.exe, we will need to redirect the output.
558 	// It is possible to connect to the parent cmd.exe console using WinAPI
559 	// (namely, AttachConsole(ATTACH_PARENT_PROCESS)). However, cmd.exe detaches
560 	// the program since it (correctly) thinks we are a GUI application, and
561 	// returns to the prompt. Thus, when we exit, it seems like we are stuck.
562 	// Another possibility is to compile as a console application. We will
563 	// always have an attached terminal in this case, even when starting from
564 	// Windows Explorer. This console can be destroyed, but it will cause it
565 	// to flash into view, then disappear right away.
566 
567 	// Flush the output in case anything is queued
568 	fclose(stdout);
569 	fclose(stderr);
570 
571 	string folderPath = Get_home() + "/";
572 
573 	string stdoutPath = folderPath + prefix + "out.txt";
574 	const char *stdoutfile = stdoutPath.c_str();
575 
576 	// Redirect standard input and standard output
577 	FILE *newfp = freopen(stdoutfile, "w", stdout);
578 	if (newfp == nullptr) {
579 		// This happens on NT
580 #if !defined(stdout)
581 		stdout = fopen(stdoutfile, "w");
582 #else
583 		newfp = fopen(stdoutfile, "w");
584 		if (newfp)
585 			*stdout = *newfp;
586 #endif
587 	}
588 
589 	string stderrPath = folderPath + prefix + "err.txt";
590 	const char *stderrfile = stderrPath.c_str();
591 
592 	newfp = freopen(stderrfile, "w", stderr);
593 	if (newfp == nullptr) {
594 		// This happens on NT
595 #if !defined(stderr)
596 		stderr = fopen(stderrfile, "w");
597 #else
598 		newfp = fopen(stderrfile, "w");
599 		if (newfp)
600 			*stderr = *newfp;
601 #endif
602 	}
603 	setvbuf(stdout, nullptr, _IOLBF, BUFSIZ);  // Line buffered
604 	setbuf(stderr, nullptr);                   // No buffering
605 }
606 
cleanup_output(const char * prefix)607 void cleanup_output(const char *prefix) {
608 	string folderPath = Get_home() + "/";
609 	if (!ftell(stdout)) {
610 		fclose(stdout);
611 		string stdoutPath = folderPath + prefix + "out.txt";
612 		remove(stdoutPath.c_str());
613 	}
614 	if (!ftell(stderr)) {
615 		fclose(stderr);
616 		string stderrPath = folderPath + prefix + "err.txt";
617 		remove(stderrPath.c_str());
618 	}
619 }
620 #endif // USE_CONSOLE
621 #endif  // _WIN32
622 
Get_home()623 string Get_home() {
624 	std::string home_dir;
625 #ifdef _WIN32
626 #ifdef PORTABLE_EXULT_WIN32
627 	home_dir = ".";
628 #else
629 	if (get_system_path("<HOME>") == ".")
630 		home_dir = ".";
631 	else {
632 		shell32_wrapper shell32;
633 		home_dir = shell32.Get_local_appdata();
634 		if (!home_dir.empty())
635 			home_dir += "\\Exult";
636 		else
637 			home_dir = ".";
638 	}
639 #endif // PORTABLE_WIN32_EXULT
640 #else
641 	const char *home = nullptr;
642 	if ((home = getenv("HOME")) != nullptr)
643 		home_dir = home;
644 #endif
645 	return home_dir;
646 }
647 
648 #if defined(MACOSX) || defined(__IPHONEOS__)
649 struct CFDeleter
650 {
operator ()CFDeleter651 	void operator()(CFTypeRef ptr) const
652 	{
653 		if (ptr) CFRelease(ptr);
654 	}
655 };
656 #endif
657 
setup_data_dir(const std::string & data_path,const char * runpath)658 void setup_data_dir(
659     const std::string &data_path,
660     const char *runpath
661 ) {
662 #if defined(MACOSX) || defined(__IPHONEOS__)
663 	// Can we try from the bundle?
664 	std::unique_ptr<std::remove_pointer<CFURLRef>::type, CFDeleter> fileUrl {std::move(CFBundleCopyResourcesDirectoryURL(CFBundleGetMainBundle()))};
665 	if (fileUrl) {
666 		unsigned char buf[MAXPATHLEN];
667 		if (CFURLGetFileSystemRepresentation(fileUrl.get(), true, buf, sizeof(buf))) {
668 			string path(reinterpret_cast<const char *>(buf));
669 			path += "/data";
670 			add_system_path("<BUNDLE>", path);
671 			if (!U7exists(BUNDLE_EXULT_FLX))
672 				clear_system_path("<BUNDLE>");
673 		}
674 	}
675 #endif
676 #ifdef __IPHONEOS__
677 	if (is_system_path_defined("<BUNDLE>")) {
678 		// We have the flxfiles in the bundle, so lets use it.
679 		// But lets use <DATA> in the iTunes file sharing.
680 		string path(ios_get_documents_dir());
681 		path += "/data";
682 		add_system_path("<DATA>", path);
683 		return;
684 	}
685 #endif
686 
687 	// First, try the cfg value:
688 	add_system_path("<DATA>", data_path);
689 	if (U7exists(EXULT_FLX))
690 		return;
691 
692 	// Now, try default -- if warranted.
693 	if (data_path != EXULT_DATADIR) {
694 		add_system_path("<DATA>", EXULT_DATADIR);
695 		if (U7exists(EXULT_FLX))
696 			return;
697 	}
698 
699 	// Try "data" subdirectory for current working directory:
700 	add_system_path("<DATA>", "data");
701 	if (U7exists(EXULT_FLX))
702 		return;
703 
704 	// Try "data" subdirectory for exe directory:
705 	const char *sep = std::strrchr(runpath, '/');
706 	if (!sep) sep = std::strrchr(runpath, '\\');
707 	if (sep) {
708 		int plen = sep - runpath;
709 		std::string dpath(runpath, plen + 1);
710 		dpath += "data";
711 		add_system_path("<DATA>", dpath);
712 	} else
713 		add_system_path("<DATA>", "data");
714 	if (U7exists(EXULT_FLX))
715 		return;
716 
717 	if (is_system_path_defined("<BUNDLE>")) {
718 		// We have the bundle, so lets use it. But lets also leave <DATA>
719 		// with a sensible default.
720 		add_system_path("<DATA>", data_path);
721 		return;
722 	}
723 
724 	// We've tried them all...
725 	std::cerr << "Could not find 'exult.flx' anywhere." << std::endl;
726 	std::cerr << "Please make sure Exult is correctly installed," << std::endl;
727 	std::cerr << "and the Exult data path is specified in the configuration file." << std::endl;
728 	std::cerr << "(See the README file for more information)" << std::endl;
729 	exit(-1);
730 }
731 
Get_config_dir(const string & home_dir)732 static string Get_config_dir(const string& home_dir) {
733 #ifdef __IPHONEOS__
734 	ignore_unused_variable_warning(home_dir);
735 	return ios_get_documents_dir();
736 #elif defined(MACOSX)
737 	string config_dir(home_dir);
738 	config_dir += "/Library/Preferences";
739 	return config_dir;
740 #else
741 	return home_dir;
742 #endif
743 }
744 
Get_savehome_dir(const string & home_dir,const string & config_dir)745 static string Get_savehome_dir(const string& home_dir, const string& config_dir) {
746 #ifdef __IPHONEOS__
747 	ignore_unused_variable_warning(home_dir);
748 	string savehome_dir(config_dir);
749 	savehome_dir += "/save";
750 	return savehome_dir;
751 #elif defined(MACOSX)
752 	ignore_unused_variable_warning(config_dir);
753 	string savehome_dir(home_dir);
754 	savehome_dir += "/Library/Application Support/Exult";
755 	return savehome_dir;
756 #elif defined(XWIN)
757 	ignore_unused_variable_warning(config_dir);
758 	string savehome_dir(home_dir);
759 	savehome_dir += "/.exult";
760 	return savehome_dir;
761 #else
762 	ignore_unused_variable_warning(config_dir);
763 	return home_dir;
764 #endif
765 }
766 
Get_gamehome_dir(const string & home_dir,const string & config_dir)767 static string Get_gamehome_dir(const string& home_dir, const string& config_dir) {
768 #ifdef __IPHONEOS__
769 	ignore_unused_variable_warning(home_dir);
770 	string gamehome_dir(config_dir);
771 	gamehome_dir += "/game";
772 	return gamehome_dir;
773 #elif defined(MACOSX)
774 	ignore_unused_variable_warning(home_dir, config_dir);
775 	return "/Library/Application Support/Exult";
776 #elif defined(XWIN)
777 	ignore_unused_variable_warning(home_dir, config_dir);
778 	return EXULT_DATADIR;
779 #else
780 	ignore_unused_variable_warning(home_dir, config_dir);
781 	return ".";
782 #endif
783 }
784 
setup_program_paths()785 void setup_program_paths() {
786 	string home_dir(Get_home());
787 	string config_dir(Get_config_dir(home_dir));
788 	string savehome_dir(Get_savehome_dir(home_dir, config_dir));
789 	string gamehome_dir(Get_gamehome_dir(home_dir, config_dir));
790 
791 	if (get_system_path("<HOME>") != ".")
792 		add_system_path("<HOME>", home_dir);
793 	add_system_path("<CONFIG>", config_dir);
794 	add_system_path("<SAVEHOME>", savehome_dir);
795 	add_system_path("<GAMEHOME>", gamehome_dir);
796 	U7mkdir("<HOME>", 0755);
797 	U7mkdir("<CONFIG>", 0755);
798 	U7mkdir("<SAVEHOME>", 0755);
799 #ifdef MACOSX
800 	// setup APPBUNDLE needed to launch Exult Studio from one
801 	std::unique_ptr<std::remove_pointer<CFURLRef>::type, CFDeleter> fileUrl {std::move(CFBundleCopyBundleURL(CFBundleGetMainBundle()))};
802 	if (fileUrl) {
803 		unsigned char buf[MAXPATHLEN];
804 		if (CFURLGetFileSystemRepresentation(fileUrl.get(), true, buf, sizeof(buf))) {
805 			string path(reinterpret_cast<const char *>(buf));
806 			add_system_path("<APPBUNDLE>", path);
807 			// test whether it is actually an app bundle
808 			path += "/Contents/Info.plist";
809 			if (!U7exists(path))
810 				clear_system_path("<APPBUNDLE>");
811 		}
812 	}
813 #endif
814 }
815 
816 /*
817  *  Change the current directory
818  */
819 
U7chdir(const char * dirname)820 int U7chdir(
821     const char *dirname // May be converted to upper-case.
822 ) {
823 	return chdir(dirname);
824 }
825 
826 /*
827  *  Copy a file. May throw an exception.
828  */
U7copy(const char * src,const char * dest)829 void U7copy(
830     const char *src,
831     const char *dest
832 ) {
833 	std::ifstream in;
834 	std::ofstream out;
835 	try {
836 		U7open(in, src);
837 		U7open(out, dest);
838 	} catch (exult_exception &e) {
839 		in.close();
840 		out.close();
841 		throw;
842 	}
843 	out << in.rdbuf();
844 	out.flush();
845 	bool inok = in.good();
846 	bool outok = out.good();
847 	in.close();
848 	out.close();
849 	if (!inok)
850 		throw file_read_exception(src);
851 	if (!outok)
852 		throw file_write_exception(dest);
853 }
854 
855 /*
856  *  Take log2 of a number.
857  *
858  *  Output: Log2 of n (0 if n==0).
859  */
860 
Log2(uint32 n)861 int Log2(
862     uint32 n
863 ) {
864 	int result = 0;
865 	for (n = n >> 1; n; n = n >> 1)
866 		result++;
867 	return result;
868 }
869 
870 /*
871  *  Get the highest power of 2 <= given number.
872  *  From http://aggregate.org/MAGIC/#Log2%20of%20an%20Integer
873  *
874  *  Output: Integer containing the highest bit of input.
875  */
876 
msb32(uint32 x)877 uint32 msb32(uint32 x) {
878 	x |= (x >>  1);
879 	x |= (x >>  2);
880 	x |= (x >>  4);
881 	x |= (x >>  8);
882 	x |= (x >> 16);
883 	return x & ~(x >> 1);
884 }
885 
886 /*
887  *  Get the lowest power of 2 >= given number.
888  *
889  *  Output: First power of 2 >= input (0 if n==0).
890  */
891 
fgepow2(uint32 n)892 int fgepow2(uint32 n) {
893 	uint32 l = msb32(n);
894 	return l < n ? (l << 1) : l;
895 }
896 
897 /*
898  *  Replacement for non-standard strdup function.
899  */
900 
newstrdup(const char * s)901 char *newstrdup(const char *s) {
902 	if (!s)
903 		throw std::invalid_argument("nullptr pointer passed to newstrdup");
904 	char *ret = new char[std::strlen(s) + 1];
905 	std::strcpy(ret, s);
906 	return ret;
907 }
908 
909 /*
910  *  Build a file name with the map directory before it; ie,
911  *      Get_mapped_name("<GAMEDAT>/ireg, 3, to) will store
912  *          "<GAMEDAT>/map03/ireg".
913  */
914 
Get_mapped_name(const char * from,int num,char * to)915 char *Get_mapped_name(
916     const char *from,
917     int num,
918     char *to
919 ) {
920 	if (num == 0)
921 		strcpy(to, from);   // Default map.
922 	else {
923 		const char *sep = strrchr(from, '/');
924 		assert(sep != nullptr);
925 		size_t len = sep - from;
926 		memcpy(to, from, len);  // Copy dir.
927 		strcpy(to + len, MULTIMAP_DIR);
928 		len = strlen(to);
929 		constexpr static const char hexLUT[] = "0123456789abcdef";
930 		to[len] = hexLUT[num / 16];
931 		to[len + 1] = hexLUT[num % 16];
932 		strcpy(to + len + 2, sep);
933 	}
934 	return to;
935 }
936 
937 /*
938  *  Find next existing map, starting with a given number.
939  *
940  *  Output: # found, or -1.
941  */
942 
Find_next_map(int start,int maxtry)943 int Find_next_map(
944     int start,          // Start here.
945     int maxtry          // Max. # to try.
946 ) {
947 	char fname[128];
948 
949 	for (int i = start; maxtry; --maxtry, ++i) {
950 		if (U7exists(Get_mapped_name("<STATIC>/", i, fname)) ||
951 		        U7exists(Get_mapped_name("<PATCH>/", i, fname)))
952 			return i;
953 	}
954 	return -1;
955 }
956 
957