| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586 | /* * 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"; }void IVCMIDirs::init(){	// TODO: Log errors	bfs::create_directories(userDataPath());	bfs::create_directories(userCachePath());	bfs::create_directories(userConfigPath());	bfs::create_directories(userSavePath());}#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;};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_APPLEclass 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{	return std::vector<bfs::path>(1, "../Data");}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 homeDir;	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;	const char* tempResult;	ret.push_back(M_DATA_DIR);	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(entry + "/vcmi");	}	else	{		ret.push_back("/usr/share/");		ret.push_back("/usr/local/share/");	}	return ret;}bfs::path VCMIDirsXDG::libraryPath() const { return M_LIB_DIR; }bfs::path VCMIDirsXDG::binaryPath() const { return M_BIN_DIR; }std::string VCMIDirsXDG::libraryName(const std::string& basename) const { return "lib" + basename + ".so"; }#ifdef VCMI_ANDROIDclass VCMIDirsAndroid : public VCMIDirsXDG{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;};// on Android HOME will be set to something like /sdcard/data/Android/is.xyz.vcmi/files/bfs::path VCMIDirsAndroid::userDataPath() const { return getenv("HOME"); }bfs::path VCMIDirsAndroid::userCachePath() const { return userDataPath() / "cache"; }bfs::path VCMIDirsAndroid::userConfigPath() const { return userDataPath() / "config"; }std::vector<bfs::path> VCMIDirsAndroid::dataPaths() const{	return std::vector<bfs::path>(1, userDataPath());}#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;	}}
 |