1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668
|
/*
* VCMIDirs.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "VCMIDirs.h"
namespace bfs = boost::filesystem;
bfs::path IVCMIDirs::userSavePath() const { return userDataPath() / "Saves"; }
bfs::path IVCMIDirs::fullLibraryPath(const std::string &desiredFolder, const std::string &baseLibName) const
{
return libraryPath() / desiredFolder / libraryName(baseLibName);
}
void IVCMIDirs::init()
{
// TODO: Log errors
bfs::create_directories(userDataPath());
bfs::create_directories(userCachePath());
bfs::create_directories(userConfigPath());
bfs::create_directories(userSavePath());
}
#ifdef VCMI_ANDROID
#include "CAndroidVMHelper.h"
#endif
#ifdef VCMI_WINDOWS
#ifdef __MINGW32__
#define _WIN32_IE 0x0500
#ifndef CSIDL_MYDOCUMENTS
#define CSIDL_MYDOCUMENTS CSIDL_PERSONAL
#endif
#endif // __MINGW32__
#include <windows.h>
#include <shlobj.h>
#include <shellapi.h>
// Generates script file named _temp.bat in 'to' directory and runs it
// Script will:
// - Wait util 'exeName' ends.
// - Copy all files from 'from' to 'to'
// - Ask user to replace files existed in 'to'.
// - Run 'exeName'
// - Delete itself.
bool StartBatchCopyDataProgram(
const bfs::path& from, const bfs::path& to, const bfs::path& exeName,
const bfs::path& currentPath = bfs::current_path())
{
static const char base[] =
"@echo off" "\n"
"echo Preparing to move VCMI data system." "\n"
":CLIENT_RUNNING_LOOP" "\n"
"TASKLIST | FIND /I %1% > nul" "\n"
"IF ERRORLEVEL 1 (" "\n"
"GOTO CLIENT_NOT_RUNNING" "\n"
") ELSE (" "\n"
"echo %1% is still running..." "\n"
"echo Waiting until process ends..." "\n"
"ping 1.1.1.1 -n 1 -w 3000 > nul" "\n" // Sleep ~3 seconds. I love Windows :)
"goto :CLIENT_RUNNING_LOOP" "\n"
")" "\n"
":CLIENT_NOT_RUNNING" "\n"
"echo %1% turned off..." "\n"
"echo Attempt to move datas." "\n"
"echo From: %2%" "\n"
"echo To: %4%" "\n"
"echo Please resolve any conflicts..." "\n"
"move /-Y %3% %4%" "\n" // Move all files from %3% to %4%.
// /-Y ask what to do when file exists in %4%
":REMOVE_OLD_DIR" "\n"
"rd %2% || rem" "\n" // Remove empty directory. Sets error flag if fail.
"IF ERRORLEVEL 145 (" "\n" // Directory not empty
"echo Directory %2% is not empty." "\n"
"echo Please move rest of files manually now." "\n"
"pause" "\n" // Press any key to continue...
"goto REMOVE_OLD_DIR" "\n"
")" "\n"
"echo Game data updated succefully." "\n"
"echo Please update your shortcuts." "\n"
"echo Press any key to start a game . . ." "\n"
"pause > nul" "\n"
"%5%" "\n"
"del \"%%~f0\"&exit" "\n" // Script deletes itself
;
const auto startGameString =
bfs::equivalent(currentPath, from) ?
(boost::format("start \"\" %1%") % (to / exeName)) : // Start game in new path.
(boost::format("start \"\" /D %1% %2%") % currentPath % (to / exeName)); // Start game in 'currentPath"
const bfs::path bathFilename = to / "_temp.bat";
bfs::ofstream bathFile(bathFilename, bfs::ofstream::trunc | bfs::ofstream::out);
if (!bathFile.is_open())
return false;
bathFile << (boost::format(base) % exeName % from % (from / "*.*") % to % startGameString.str()).str();
bathFile.close();
std::system(("start \"Updating VCMI datas\" /D \"" + to.string() + "\" \"" + bathFilename.string() + '\"').c_str());
// start won't block std::system
// /D start bat in other directory insteand of current directory.
return true;
}
class VCMIDirsWIN32 final : public IVCMIDirs
{
public:
boost::filesystem::path userDataPath() const override;
boost::filesystem::path userCachePath() const override;
boost::filesystem::path userConfigPath() const override;
std::vector<boost::filesystem::path> dataPaths() const override;
boost::filesystem::path clientPath() const override;
boost::filesystem::path serverPath() const override;
boost::filesystem::path libraryPath() const override;
boost::filesystem::path binaryPath() const override;
std::string libraryName(const std::string& basename) const override;
std::string genHelpString() const override;
void init() override;
protected:
boost::filesystem::path oldUserDataPath() const;
boost::filesystem::path oldUserSavePath() const;
};
void VCMIDirsWIN32::init()
{
// Call base (init dirs)
IVCMIDirs::init();
// Moves one directory (from) contents to another directory (to)
// Shows user the "moving file dialog" and ask to resolve conflits.
// If necessary updates current directory.
auto moveDirIfExists = [](const bfs::path& from, const bfs::path& to) -> bool
{
if (!bfs::is_directory(from))
return true; // Nothing to do here. Flies away.
if (bfs::is_empty(from))
{
if (bfs::current_path() == from)
bfs::current_path(to);
bfs::remove(from);
return true; // Nothing to do here. Flies away.
}
if (!bfs::is_directory(to))
{
// IVCMIDirs::init() should create all destination directories.
// TODO: Log fact, that we shouldn't be here.
bfs::create_directories(to);
}
// Why the hell path strings should be end with double null :/
auto makeDoubleNulled = [](const bfs::path& path) -> std::unique_ptr<wchar_t[]>
{
const std::wstring& pathStr = path.native();
std::unique_ptr<wchar_t[]> result(new wchar_t[pathStr.length() + 2]);
size_t i = 0;
for (const wchar_t ch : pathStr)
result[i++] = ch;
result[i++] = L'\0';
result[i++] = L'\0';
return result;
};
auto fromDNulled = makeDoubleNulled(from / L"*.*");
auto toDNulled = makeDoubleNulled(to);
SHFILEOPSTRUCTW fileOp;
fileOp.hwnd = GetConsoleWindow();
fileOp.wFunc = FO_MOVE;
fileOp.pFrom = fromDNulled.get();
fileOp.pTo = toDNulled.get();
fileOp.fFlags = 0;
fileOp.hNameMappings = nullptr;
fileOp.lpszProgressTitle = nullptr;
const int errorCode = SHFileOperationW(&fileOp);
if (errorCode != 0) // TODO: Log error. User should try to move files.
return false;
else if (fileOp.fAnyOperationsAborted) // TODO: Log warn. User aborted operation. User should move files.
return false;
else if (!bfs::is_empty(from)) // TODO: Log warn. Some files not moved. User should try to move files.
return false;
if (bfs::current_path() == from)
bfs::current_path(to);
// TODO: Log fact that we moved files succefully.
bfs::remove(from);
return true;
};
// Retrieves the fully qualified path for the file that contains the specified module.
// The module must have been loaded by the current process.
// If this parameter is nullptr, retrieves the path of the executable file of the current process.
auto getModulePath = [](HMODULE hModule) -> bfs::path
{
wchar_t exePathW[MAX_PATH];
DWORD nSize = GetModuleFileNameW(hModule, exePathW, MAX_PATH);
DWORD error = GetLastError();
// WARN: Windows XP don't set ERROR_INSUFFICIENT_BUFFER error.
if (nSize != 0 && error != ERROR_INSUFFICIENT_BUFFER)
return bfs::path(std::wstring(exePathW, nSize));
// TODO: Error handling
return bfs::path();
};
// Moves one directory contents to another directory
// Shows user the "moving file dialog" and ask to resolve conflicts.
// It takes into account that 'from' path can contain current executable.
// If necessary closes program and starts update script.
auto advancedMoveDirIfExists = [getModulePath, moveDirIfExists](const bfs::path& from, const bfs::path& to) -> bool
{
const bfs::path executablePath = getModulePath(nullptr);
// VCMI cann't determine executable path.
// Use standard way to move directory and exit function.
if (executablePath.empty())
return moveDirIfExists(from, to);
const bfs::path executableName = executablePath.filename();
// Current executabl isn't in 'from' path.
// Use standard way to move directory and exit function.
if (!bfs::equivalent(executablePath, from / executableName))
return moveDirIfExists(from, to);
// Try standard way to move directory.
// I don't know how other systems, but Windows 8.1 allow to move running executable.
if (moveDirIfExists(from, to))
return true;
// Start copying script and exit program.
if (StartBatchCopyDataProgram(from, to, executableName))
exit(ERROR_SUCCESS);
// Everything failed :C
return false;
};
moveDirIfExists(oldUserSavePath(), userSavePath());
advancedMoveDirIfExists(oldUserDataPath(), userDataPath());
}
bfs::path VCMIDirsWIN32::userDataPath() const
{
wchar_t profileDir[MAX_PATH];
if (SHGetSpecialFolderPathW(nullptr, profileDir, CSIDL_MYDOCUMENTS, FALSE) != FALSE)
return bfs::path(profileDir) / "My Games" / "vcmi";
return ".";
}
bfs::path VCMIDirsWIN32::oldUserDataPath() const
{
wchar_t profileDir[MAX_PATH];
if (SHGetSpecialFolderPathW(nullptr, profileDir, CSIDL_PROFILE, FALSE) == FALSE) // WinAPI way failed
{
#if defined(_MSC_VER) && _MSC_VER >= 1700
wchar_t* buffer;
size_t bufferSize;
errno_t result = _wdupenv_s(&buffer, &bufferSize, L"userprofile");
if (result == 0)
{
bfs::path result(std::wstring(buffer, bufferSize));
free(buffer);
return result;
}
#else
const char* profileDirA;
if ((profileDirA = std::getenv("userprofile"))) // STL way succeed
return bfs::path(profileDirA) / "vcmi";
#endif
else
return "."; // Every thing failed, return current directory.
}
else
return bfs::path(profileDir) / "vcmi";
//return dataPaths()[0] ???;
}
bfs::path VCMIDirsWIN32::oldUserSavePath() const { return userDataPath() / "Games"; }
bfs::path VCMIDirsWIN32::userCachePath() const { return userDataPath(); }
bfs::path VCMIDirsWIN32::userConfigPath() const { return userDataPath() / "config"; }
std::vector<bfs::path> VCMIDirsWIN32::dataPaths() const
{
return std::vector<bfs::path>(1, bfs::path("."));
}
bfs::path VCMIDirsWIN32::clientPath() const { return binaryPath() / "VCMI_client.exe"; }
bfs::path VCMIDirsWIN32::serverPath() const { return binaryPath() / "VCMI_server.exe"; }
bfs::path VCMIDirsWIN32::libraryPath() const { return "."; }
bfs::path VCMIDirsWIN32::binaryPath() const { return "."; }
std::string VCMIDirsWIN32::genHelpString() const
{
std::vector<std::string> tempVec;
for (const bfs::path& path : dataPaths())
tempVec.push_back(path.string());
std::string gdStringA = boost::algorithm::join(tempVec, ";");
return
" game data: " + gdStringA + "\n"
" libraries: " + libraryPath().string() + "\n"
" server: " + serverPath().string() + "\n"
"\n"
" user data: " + userDataPath().string() + "\n"
" user cache: " + userCachePath().string() + "\n"
" user config: " + userConfigPath().string() + "\n"
" user saves: " + userSavePath().string() + "\n"; // Should end without new-line?
}
std::string VCMIDirsWIN32::libraryName(const std::string& basename) const { return basename + ".dll"; }
#elif defined(VCMI_UNIX)
class IVCMIDirsUNIX : public IVCMIDirs
{
public:
boost::filesystem::path clientPath() const override;
boost::filesystem::path serverPath() const override;
std::string genHelpString() const override;
bool developmentMode() const;
};
bool IVCMIDirsUNIX::developmentMode() const
{
// We want to be able to run VCMI from single directory. E.g to run from build output directory
return bfs::exists("AI") && bfs::exists("config") && bfs::exists("Mods") && bfs::exists("vcmiserver") && bfs::exists("vcmiclient");
}
bfs::path IVCMIDirsUNIX::clientPath() const { return binaryPath() / "vcmiclient"; }
bfs::path IVCMIDirsUNIX::serverPath() const { return binaryPath() / "vcmiserver"; }
std::string IVCMIDirsUNIX::genHelpString() const
{
std::vector<std::string> tempVec;
for (const bfs::path& path : dataPaths())
tempVec.push_back(path.string());
std::string gdStringA = boost::algorithm::join(tempVec, ":");
return
" game data: " + gdStringA + "\n"
" libraries: " + libraryPath().string() + "\n"
" server: " + serverPath().string() + "\n"
"\n"
" user data: " + userDataPath().string() + "\n"
" user cache: " + userCachePath().string() + "\n"
" user config: " + userConfigPath().string() + "\n"
" user saves: " + userSavePath().string() + "\n"; // Should end without new-line?
}
#ifdef VCMI_APPLE
class VCMIDirsOSX final : public IVCMIDirsUNIX
{
public:
boost::filesystem::path userDataPath() const override;
boost::filesystem::path userCachePath() const override;
boost::filesystem::path userConfigPath() const override;
std::vector<boost::filesystem::path> dataPaths() const override;
boost::filesystem::path libraryPath() const override;
boost::filesystem::path binaryPath() const override;
std::string libraryName(const std::string& basename) const override;
void init() override;
};
void VCMIDirsOSX::init()
{
// Call base (init dirs)
IVCMIDirsUNIX::init();
auto moveDirIfExists = [](const bfs::path& from, const bfs::path& to)
{
if (!bfs::is_directory(from))
return; // Nothing to do here. Flies away.
if (bfs::is_empty(from))
{
bfs::remove(from);
return; // Nothing to do here. Flies away.
}
if (!bfs::is_directory(to))
{
// IVCMIDirs::init() should create all destination directories.
// TODO: Log fact, that we shouldn't be here.
bfs::create_directories(to);
}
for (bfs::directory_iterator file(from); file != bfs::directory_iterator(); ++file)
{
const boost::filesystem::path& srcFilePath = file->path();
const boost::filesystem::path dstFilePath = to / srcFilePath.filename();
// TODO: Aplication should ask user what to do when file exists:
// replace/ignore/stop process/replace all/ignore all
if (!boost::filesystem::exists(dstFilePath))
bfs::rename(srcFilePath, dstFilePath);
}
if (!bfs::is_empty(from)); // TODO: Log warn. Some files not moved. User should try to move files.
else
bfs::remove(from);
};
moveDirIfExists(userDataPath() / "Games", userSavePath());
}
bfs::path VCMIDirsOSX::userDataPath() const
{
// This is Cocoa code that should be normally used to get path to Application Support folder but can't use it here for now...
// NSArray* urls = [[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask];
// UserPath = path([urls[0] path] + "/vcmi").string();
// ...so here goes a bit of hardcode instead
const char* homeDir = getenv("HOME"); // Should be std::getenv?
if (homeDir == nullptr)
homeDir = ".";
return bfs::path(homeDir) / "Library" / "Application Support" / "vcmi";
}
bfs::path VCMIDirsOSX::userCachePath() const { return userDataPath(); }
bfs::path VCMIDirsOSX::userConfigPath() const { return userDataPath() / "config"; }
std::vector<bfs::path> VCMIDirsOSX::dataPaths() const
{
std::vector<bfs::path> ret;
//FIXME: need some proper codepath for detecting running from build output directory
if(developmentMode())
{
ret.push_back(".");
}
else
{
ret.push_back("../Resources/Data");
}
return ret;
}
bfs::path VCMIDirsOSX::libraryPath() const { return "."; }
bfs::path VCMIDirsOSX::binaryPath() const { return "."; }
std::string VCMIDirsOSX::libraryName(const std::string& basename) const { return "lib" + basename + ".dylib"; }
#elif defined(VCMI_XDG)
class VCMIDirsXDG : public IVCMIDirsUNIX
{
public:
boost::filesystem::path userDataPath() const override;
boost::filesystem::path userCachePath() const override;
boost::filesystem::path userConfigPath() const override;
std::vector<boost::filesystem::path> dataPaths() const override;
boost::filesystem::path libraryPath() const override;
boost::filesystem::path binaryPath() const override;
std::string libraryName(const std::string& basename) const override;
};
bfs::path VCMIDirsXDG::userDataPath() const
{
// $XDG_DATA_HOME, default: $HOME/.local/share
const char* homeDir;
if((homeDir = getenv("XDG_DATA_HOME")))
return bfs::path(homeDir) / "vcmi";
else if((homeDir = getenv("HOME")))
return bfs::path(homeDir) / ".local" / "share" / "vcmi";
else
return ".";
}
bfs::path VCMIDirsXDG::userCachePath() const
{
// $XDG_CACHE_HOME, default: $HOME/.cache
const char * tempResult;
if ((tempResult = getenv("XDG_CACHE_HOME")))
return bfs::path(tempResult) / "vcmi";
else if ((tempResult = getenv("HOME")))
return bfs::path(tempResult) / ".cache" / "vcmi";
else
return ".";
}
bfs::path VCMIDirsXDG::userConfigPath() const
{
// $XDG_CONFIG_HOME, default: $HOME/.config
const char * tempResult;
if ((tempResult = getenv("XDG_CONFIG_HOME")))
return bfs::path(tempResult) / "vcmi";
else if ((tempResult = getenv("HOME")))
return bfs::path(tempResult) / ".config" / "vcmi";
else
return ".";
}
std::vector<bfs::path> VCMIDirsXDG::dataPaths() const
{
// $XDG_DATA_DIRS, default: /usr/local/share/:/usr/share/
// construct list in reverse.
// in specification first directory has highest priority
// in vcmi fs last directory has highest priority
std::vector<bfs::path> ret;
if(developmentMode())
{
//For now we'll disable usage of system directories when VCMI running from bin directory
ret.push_back(".");
}
else
{
ret.push_back(M_DATA_DIR);
const char * tempResult;
if((tempResult = getenv("XDG_DATA_DIRS")) != nullptr)
{
std::string dataDirsEnv = tempResult;
std::vector<std::string> dataDirs;
boost::split(dataDirs, dataDirsEnv, boost::is_any_of(":"));
for (auto & entry : boost::adaptors::reverse(dataDirs))
ret.push_back(bfs::path(entry) / "vcmi");
}
else
{
ret.push_back(bfs::path("/usr/share") / "vcmi");
ret.push_back(bfs::path("/usr/local/share") / "vcmi");
}
// Debian and other distributions might want to use it while it's not part of XDG
ret.push_back(bfs::path("/usr/share/games") / "vcmi");
}
return ret;
}
bfs::path VCMIDirsXDG::libraryPath() const
{
if(developmentMode())
return ".";
else
return M_LIB_DIR;
}
bfs::path VCMIDirsXDG::binaryPath() const
{
if(developmentMode())
return ".";
else
return M_BIN_DIR;
}
std::string VCMIDirsXDG::libraryName(const std::string& basename) const { return "lib" + basename + ".so"; }
#ifdef VCMI_ANDROID
class VCMIDirsAndroid : public VCMIDirsXDG
{
std::string basePath;
std::string internalPath;
std::string nativePath;
public:
bfs::path fullLibraryPath(const std::string & desiredFolder, const std::string & baseLibName) const override;
bfs::path libraryPath() const override;
bfs::path userDataPath() const override;
bfs::path userCachePath() const override;
bfs::path userConfigPath() const override;
std::vector<bfs::path> dataPaths() const override;
void init() override;
};
bfs::path VCMIDirsAndroid::libraryPath() const { return nativePath; }
bfs::path VCMIDirsAndroid::userDataPath() const { return basePath; }
bfs::path VCMIDirsAndroid::userCachePath() const { return userDataPath() / "cache"; }
bfs::path VCMIDirsAndroid::userConfigPath() const { return userDataPath() / "config"; }
bfs::path VCMIDirsAndroid::fullLibraryPath(const std::string & desiredFolder, const std::string & baseLibName) const
{
// ignore passed folder (all libraries in android are dumped into a single folder)
return libraryPath() / libraryName(baseLibName);
}
std::vector<bfs::path> VCMIDirsAndroid::dataPaths() const
{
std::vector<bfs::path> paths(2);
paths.push_back(internalPath);
paths.push_back(userDataPath());
return paths;
}
void VCMIDirsAndroid::init()
{
// asks java code to retrieve needed paths from environment
CAndroidVMHelper envHelper;
basePath = envHelper.callStaticStringMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "dataRoot");
internalPath = envHelper.callStaticStringMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "internalDataRoot");
nativePath = envHelper.callStaticStringMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "nativePath");
IVCMIDirs::init();
}
#endif // VCMI_ANDROID
#endif // VCMI_APPLE, VCMI_XDG
#endif // VCMI_WINDOWS, VCMI_UNIX
// Getters for interfaces are separated for clarity.
namespace VCMIDirs
{
const IVCMIDirs& get()
{
#ifdef VCMI_WINDOWS
static VCMIDirsWIN32 singleton;
#elif defined(VCMI_ANDROID)
static VCMIDirsAndroid singleton;
#elif defined(VCMI_XDG)
static VCMIDirsXDG singleton;
#elif defined(VCMI_APPLE)
static VCMIDirsOSX singleton;
#endif
static bool initialized = false;
if (!initialized)
{
#ifndef VCMI_ANDROID
std::locale::global(boost::locale::generator().generate("en_US.UTF-8"));
#endif
boost::filesystem::path::imbue(std::locale());
singleton.init();
initialized = true;
}
return singleton;
}
}
|