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