浏览代码

- fixed save games issues (#1044)
- hopefully some speedup for filesystem loading (#1048)

Ivan Savenko 13 年之前
父节点
当前提交
8040a81eec

+ 4 - 1
client/AdventureMapClasses.cpp

@@ -537,7 +537,10 @@ void CMinimap::moveAdvMapSelection()
 	int3 newLocation = translateMousePosition();
 	int3 newLocation = translateMousePosition();
 	adventureInt->centerOn(newLocation);
 	adventureInt->centerOn(newLocation);
 
 
-	GH.totalRedraw(); //redraw this as well as adventure map (which may be inactive)
+	if (!(adventureInt->active & GENERAL))
+		GH.totalRedraw(); //redraw this as well as inactive adventure map
+	else
+		redraw();//redraw only this
 }
 }
 
 
 void CMinimap::clickLeft(tribool down, bool previousState)
 void CMinimap::clickLeft(tribool down, bool previousState)

+ 2 - 2
client/CPlayerInterface.cpp

@@ -165,13 +165,13 @@ void CPlayerInterface::yourTurn()
 			{
 			{
 				int index = getLastIndex("Newgame_Autosave_");
 				int index = getLastIndex("Newgame_Autosave_");
 				index %= SAVES_COUNT;
 				index %= SAVES_COUNT;
-				cb->save("Newgame_Autosave_" + boost::lexical_cast<std::string>(index + 1));
+				cb->save("Saves/Newgame_Autosave_" + boost::lexical_cast<std::string>(index + 1));
 			}
 			}
 			firstCall = 0;
 			firstCall = 0;
 		}
 		}
 		else
 		else
 		{
 		{
-			LOCPLINT->cb->save("Autosave_" + boost::lexical_cast<std::string>(autosaveCount++ + 1));
+			LOCPLINT->cb->save("Saves/Autosave_" + boost::lexical_cast<std::string>(autosaveCount++ + 1));
 			autosaveCount %= 5;
 			autosaveCount %= 5;
 		}
 		}
 
 

+ 5 - 7
client/CPreGame.cpp

@@ -843,13 +843,13 @@ void CSelectionScreen::startGame()
 		if(!(sel && sel->txt && sel->txt->text.size()))
 		if(!(sel && sel->txt && sel->txt->text.size()))
 			return;
 			return;
 
 
-		selectedName = GVCMIDirs.UserPath + "/Games/" + sel->txt->text + ".vlgm1";
+		selectedName = "Saves/" + sel->txt->text;
 
 
 		CFunctionList<void()> overWrite;
 		CFunctionList<void()> overWrite;
-		overWrite += boost::bind(&CCallback::save, LOCPLINT->cb, sel->txt->text);
+		overWrite += boost::bind(&CCallback::save, LOCPLINT->cb, selectedName);
 		overWrite += bind(&CGuiHandler::popIntTotally, &GH, this);
 		overWrite += bind(&CGuiHandler::popIntTotally, &GH, this);
 
 
-		if(fs::exists(selectedName))
+		if(CResourceHandler::get()->existsResource(ResourceID(selectedName, EResType::LIB_SAVEGAME)))
 		{
 		{
 			std::string hlp = CGI->generaltexth->allTexts[493]; //%s exists. Overwrite?
 			std::string hlp = CGI->generaltexth->allTexts[493]; //%s exists. Overwrite?
 			boost::algorithm::replace_first(hlp, "%s", sel->txt->text);
 			boost::algorithm::replace_first(hlp, "%s", sel->txt->text);
@@ -857,9 +857,6 @@ void CSelectionScreen::startGame()
 		}
 		}
 		else
 		else
 			overWrite();
 			overWrite();
-
-// 		LOCPLINT->cb->save(sel->txt->text);
-// 		GH.popIntTotally(this);
 	}
 	}
 }
 }
 
 
@@ -1417,7 +1414,8 @@ void SelectionTab::printMaps(SDL_Surface *to)
 		}
 		}
 		else
 		else
 		{
 		{
-			name = fs::basename(currentItem->fileURI);
+			name = CFileInfo(CResourceHandler::get()->getResourceName(
+			                     ResourceID(currentItem->fileURI, EResType::LIB_SAVEGAME))).getBaseName();
 		}
 		}
 
 
 		//print name
 		//print name

+ 7 - 1
client/NetPacksClient.cpp

@@ -1,6 +1,8 @@
 #include "StdInc.h"
 #include "StdInc.h"
 #include "../lib/NetPacks.h"
 #include "../lib/NetPacks.h"
 
 
+#include "../lib/Filesystem/CResourceLoader.h"
+#include "../lib/Filesystem/CFileInfo.h"
 #include "../CCallback.h"
 #include "../CCallback.h"
 #include "Client.h"
 #include "Client.h"
 #include "CPlayerInterface.h"
 #include "CPlayerInterface.h"
@@ -759,9 +761,13 @@ void YourTurn::applyCl( CClient *cl )
 
 
 void SaveGame::applyCl(CClient *cl)
 void SaveGame::applyCl(CClient *cl)
 {
 {
+	tlog1 << "Saving to " << fname << "\n";
+	CFileInfo info(fname);
+	CResourceHandler::get()->createResource(info.getStem() + ".vcgm1");
+
 	try
 	try
 	{
 	{
-		CSaveFile save(GVCMIDirs.UserPath + "/Games/" + fname + ".vcgm1");
+		CSaveFile save(CResourceHandler::get()->getResourceName(ResourceID(info.getStem(), EResType::CLIENT_SAVEGAME)));
 		save << *cl;
 		save << *cl;
 	}
 	}
 	catch(std::exception &e)
 	catch(std::exception &e)

+ 8 - 15
config/filesystem.json

@@ -13,15 +13,13 @@
 		[
 		[
 			{"type" : "file", "path" : "ALL/Data/H3ab_bmp.lod"},
 			{"type" : "file", "path" : "ALL/Data/H3ab_bmp.lod"},
 			{"type" : "file", "path" : "ALL/Data/H3bitmap.lod"},
 			{"type" : "file", "path" : "ALL/Data/H3bitmap.lod"},
-			{"type" : "dir",  "path" : "GLOBAL/Data"},
-			{"type" : "dir",  "path" : "LOCAL/Data"}
+			{"type" : "dir",  "path" : "ALL/Data"}
 		],
 		],
 		"SPRITES/":
 		"SPRITES/":
 		[
 		[
 			{"type" : "file", "path" : "ALL/Data/H3ab_spr.lod"},
 			{"type" : "file", "path" : "ALL/Data/H3ab_spr.lod"},
 			{"type" : "file", "path" : "ALL/Data/H3sprite.lod"},
 			{"type" : "file", "path" : "ALL/Data/H3sprite.lod"},
-			{"type" : "dir",  "path" : "GLOBAL/Sprites"},
-			{"type" : "dir",  "path" : "LOCAL/Sprites"}
+			{"type" : "dir",  "path" : "ALL/Sprites"}
 		],
 		],
 		"SOUNDS/":
 		"SOUNDS/":
 		[
 		[
@@ -29,35 +27,30 @@
 			{"type" : "file", "path" : "ALL/Data/Heroes3-cd2.snd"},
 			{"type" : "file", "path" : "ALL/Data/Heroes3-cd2.snd"},
 			{"type" : "file", "path" : "ALL/Data/Heroes3.snd"},
 			{"type" : "file", "path" : "ALL/Data/Heroes3.snd"},
 			//WoG have overriden sounds with .82m extension in Data
 			//WoG have overriden sounds with .82m extension in Data
-			{"type" : "dir",  "path" : "GLOBAL/Data"},
-			{"type" : "dir",  "path" : "LOCAL/Data"}
+			{"type" : "dir",  "path" : "ALL/Data"}
 		],
 		],
 		"MUSIC/":
 		"MUSIC/":
 		[
 		[
-			{"type" : "dir",  "path" : "GLOBAL/Mp3"},
-			{"type" : "dir",  "path" : "LOCAL/Mp3"}
+			{"type" : "dir",  "path" : "ALL/Mp3"}
 		],
 		],
 		"VIDEO/":
 		"VIDEO/":
 		[
 		[
 			{"type" : "file", "path" : "ALL/Data/H3ab_ahd.vid"},
 			{"type" : "file", "path" : "ALL/Data/H3ab_ahd.vid"},
 			{"type" : "file", "path" : "ALL/Data/H3ab_ahd.vid"},
 			{"type" : "file", "path" : "ALL/Data/H3ab_ahd.vid"},
 			// Location of video files in linux release
 			// Location of video files in linux release
-			{"type" : "dir",  "path" : "GLOBAL/Data/Video"},
-			{"type" : "dir",  "path" : "LOCAL/Data/Video"}
+			{"type" : "dir",  "path" : "ALL/Data/Video"}
 		],
 		],
 		"CONFIG/":
 		"CONFIG/":
 		[
 		[
-			{"type" : "dir",  "path" : "GLOBAL/Config"},
-			{"type" : "dir",  "path" : "LOCAL/Config"}
+			{"type" : "dir",  "path" : "ALL/Config", "writeable": true}
 		],
 		],
 		"MAPS/":
 		"MAPS/":
 		[
 		[
-			{"type" : "dir",  "path" : "GLOBAL/Maps"},
-			{"type" : "dir",  "path" : "LOCAL/Maps"}
+			{"type" : "dir",  "path" : "ALL/Maps"}
 		],
 		],
 		"SAVES/":
 		"SAVES/":
 		[
 		[
-			{"type" : "dir",  "path" : "LOCAL/Games"},
+			{"type" : "dir",  "path" : "LOCAL/Games", "writeable": true},
 		]
 		]
 	}
 	}
 }
 }

+ 1 - 1
lib/CStopWatch.h

@@ -48,7 +48,7 @@ public:
 	}
 	}
 	si64 memDif()
 	si64 memDif()
 	{
 	{
-		return clock()-mem;
+		return (clock()-mem) / TO_MS_DIVISOR;
 	}
 	}
 
 
 private:
 private:

+ 5 - 44
lib/Filesystem/CFileInfo.cpp

@@ -70,16 +70,18 @@ std::string CFileInfo::getStem() const
 
 
 std::string CFileInfo::getBaseName() const
 std::string CFileInfo::getBaseName() const
 {
 {
-	size_t end = name.find_last_of("/.");
 	size_t begin = name.find_last_of("/");
 	size_t begin = name.find_last_of("/");
+	size_t end = name.find_last_of("/.");
 
 
-	if(end != std::string::npos && name[end] == '.')
+	if(end != std::string::npos && name[end] == '/')
 		end = std::string::npos;
 		end = std::string::npos;
 
 
 	if(begin == std::string::npos)
 	if(begin == std::string::npos)
 		begin = 0;
 		begin = 0;
+	else
+		begin++;
 
 
-	return name.substr(begin, end);
+	return name.substr(begin, end - begin);
 }
 }
 
 
 
 
@@ -92,44 +94,3 @@ std::time_t CFileInfo::getDate() const
 {
 {
 	return boost::filesystem::last_write_time(name);
 	return boost::filesystem::last_write_time(name);
 }
 }
-
-std::unique_ptr<std::list<CFileInfo> > CFileInfo::listFiles(size_t depth, const std::string & extensionFilter /*= ""*/) const
-{
-	std::unique_ptr<std::list<CFileInfo> > fileListPtr;
-
-	if(exists() && isDirectory())
-	{
-		std::list<CFileInfo> * fileList = new std::list<CFileInfo>;
-
-		std::vector<std::string> path;
-
-		boost::filesystem::recursive_directory_iterator enddir;
-		boost::filesystem::recursive_directory_iterator it(name, boost::filesystem::symlink_option::recurse);
-
-		for(; it != enddir; ++it)
-		{
-			if (boost::filesystem::is_directory(it->status()))
-			{
-				path.resize(it.level()+1);
-				path.back() = it->path().leaf().string();
-				it.no_push(depth <= it.level());
-			}
-
-			if(extensionFilter.empty() || it->path().extension() == extensionFilter)
-			{
-				std::string filename;
-				for (size_t i=0; i<it.level() && i<path.size(); i++)
-					filename += path[i] + '/';
-				filename += it->path().leaf().string();
-
-				//tlog1 << "Found file: " << filename << "\n";
-				CFileInfo file(filename);
-				fileList->push_back(file);
-			}
-		}
-
-		fileListPtr.reset(fileList);
-	}
-
-	return fileListPtr;
-}

+ 0 - 12
lib/Filesystem/CFileInfo.h

@@ -108,18 +108,6 @@ public:
 	 */
 	 */
 	std::time_t getDate() const;
 	std::time_t getDate() const;
 
 
-	/**
-	 * Returns a list of pathnames denoting the files in the directory denoted by this pathname.
-	 *
-	 * If the pathname of this directory is absolute, then the file info pathnames are absolute as well. If the pathname of this directory is relative
-	 * then the file info pathnames are relative to the basedir as well.
-	 *
-	 * @param extensionFilter Filters files by the given extension. Optional. Empty string if all files and directories in the directory should be listed.
-	 * @return a list of pathnames denoting the files and directories in the directory denoted by this pathname as a unique ptr.
-	 * The array will be empty if the directory is empty. Ptr is null if the directory doesn't exist or if it isn't a directory.
-	 */
-	std::unique_ptr<std::list<CFileInfo> > listFiles(size_t depth, const std::string & extensionFilter = "") const;
-
 private:
 private:
 	/** Contains the original URI(not modified) e.g. ./dir/foo.txt */
 	/** Contains the original URI(not modified) e.g. ./dir/foo.txt */
 	std::string name;
 	std::string name;

+ 62 - 34
lib/Filesystem/CFilesystemLoader.cpp

@@ -4,31 +4,10 @@
 #include "CFileInfo.h"
 #include "CFileInfo.h"
 #include "CFileInputStream.h"
 #include "CFileInputStream.h"
 
 
-CFilesystemLoader::CFilesystemLoader()
+CFilesystemLoader::CFilesystemLoader(const std::string & baseDirectory, size_t depth, bool initial):
+    baseDirectory(baseDirectory),
+    fileList(listFiles(depth, initial))
 {
 {
-
-}
-
-CFilesystemLoader::CFilesystemLoader(const std::string & baseDirectory, size_t depth)
-{
-	open(baseDirectory, depth);
-}
-
-CFilesystemLoader::CFilesystemLoader(const CFileInfo & baseDirectory, size_t depth)
-{
-	open(baseDirectory.getName(), depth);
-}
-
-void CFilesystemLoader::open(const std::string & baseDirectory, size_t depth)
-{
-	// Indexes all files in the directory and store them
-	this->baseDirectory = baseDirectory;
-	CFileInfo directory(baseDirectory);
-	std::unique_ptr<std::list<CFileInfo> > fileList = directory.listFiles(depth);
-	if(fileList)
-	{
-		this->fileList = std::move(*fileList);
-	}
 }
 }
 
 
 std::unique_ptr<CInputStream> CFilesystemLoader::load(const std::string & resourceName) const
 std::unique_ptr<CInputStream> CFilesystemLoader::load(const std::string & resourceName) const
@@ -41,7 +20,7 @@ bool CFilesystemLoader::existsEntry(const std::string & resourceName) const
 {
 {
 	for(auto it = fileList.begin(); it != fileList.end(); ++it)
 	for(auto it = fileList.begin(); it != fileList.end(); ++it)
 	{
 	{
-		if(it->getName() == resourceName)
+		if(it->second == resourceName)
 		{
 		{
 			return true;
 			return true;
 		}
 		}
@@ -50,19 +29,68 @@ bool CFilesystemLoader::existsEntry(const std::string & resourceName) const
 	return false;
 	return false;
 }
 }
 
 
-std::list<std::string> CFilesystemLoader::getEntries() const
+std::unordered_map<ResourceID, std::string> CFilesystemLoader::getEntries() const
 {
 {
-	std::list<std::string> retList;
-
-	for(auto it = fileList.begin(); it != fileList.end(); ++it)
-	{
-		retList.push_back(it->getName());
-	}
-
-	return std::move(retList);
+	return fileList;
 }
 }
 
 
 std::string CFilesystemLoader::getOrigin() const
 std::string CFilesystemLoader::getOrigin() const
 {
 {
 	return baseDirectory;
 	return baseDirectory;
 }
 }
+
+bool CFilesystemLoader::createEntry(std::string filename)
+{
+	ResourceID res(filename);
+	if (fileList.find(res) != fileList.end())
+		return false;
+
+	std::ofstream file(baseDirectory + '/' + filename);
+	if (!file.good())
+		return false;
+
+	fileList[res] = filename;
+	return true;
+}
+
+
+std::unordered_map<ResourceID, std::string> CFilesystemLoader::listFiles(size_t depth, bool initial) const
+{
+
+	assert(boost::filesystem::is_directory(baseDirectory));
+	std::unordered_map<ResourceID, std::string> fileList;
+
+	std::vector<std::string> path;//vector holding relative path to our file
+
+	boost::filesystem::recursive_directory_iterator enddir;
+	boost::filesystem::recursive_directory_iterator it(baseDirectory, boost::filesystem::symlink_option::recurse);
+
+	for(; it != enddir; ++it)
+	{
+		EResType::Type type;
+
+		if (boost::filesystem::is_directory(it->status()))
+		{
+			path.resize(it.level()+1);
+			path.back() = it->path().leaf().string();
+			it.no_push(depth <= it.level()); // don't iterate into directory if depth limit reached
+
+			type = EResType::DIRECTORY;
+		}
+		else
+			type = EResTypeHelper::getTypeFromExtension(boost::filesystem::extension(*it));
+
+		if (!initial || type == EResType::DIRECTORY || type == EResType::ARCHIVE || type == EResType::TEXT)
+		{
+			//reconstruct relative filename (not possible via boost AFAIK)
+			std::string filename;
+			for (size_t i=0; i<it.level() && i<path.size(); i++)
+				filename += path[i] + '/';
+			filename += it->path().leaf().string();
+
+			fileList[ResourceID(filename, type)] = filename;
+		}
+	}
+
+	return fileList;
+}

+ 21 - 27
lib/Filesystem/CFilesystemLoader.h

@@ -12,6 +12,7 @@
 #pragma once
 #pragma once
 
 
 #include "ISimpleResourceLoader.h"
 #include "ISimpleResourceLoader.h"
+#include "CResourceLoader.h"
 
 
 class CFileInfo;
 class CFileInfo;
 class CInputStream;
 class CInputStream;
@@ -22,11 +23,6 @@ class CInputStream;
 class DLL_LINKAGE CFilesystemLoader : public ISimpleResourceLoader
 class DLL_LINKAGE CFilesystemLoader : public ISimpleResourceLoader
 {
 {
 public:
 public:
-	/**
-	 * Default c-tor.
-	 */
-	CFilesystemLoader();
-
 	/**
 	/**
 	 * Ctor.
 	 * Ctor.
 	 *
 	 *
@@ -35,25 +31,7 @@ public:
 	 *
 	 *
 	 * @throws std::runtime_error if the base directory is not a directory or if it is not available
 	 * @throws std::runtime_error if the base directory is not a directory or if it is not available
 	 */
 	 */
-	explicit CFilesystemLoader(const std::string & baseDirectory, size_t depth = 16);
-
-	/**
-	 * Ctor.
-	 *
-	 * @param baseDirectory Specifies the base directory and their sub-directories which should be indexed.
-	 *
-	 * @throws std::runtime_error if the base directory is not a directory or if it is not available
-	 */
-	explicit CFilesystemLoader(const CFileInfo & baseDirectory, size_t depth = 16);
-
-	/**
-	 * Opens a base directory to be read and indexed.
-	 *
-	 * @param baseDirectory Specifies the base directory and their sub-directories which should be indexed.
-	 *
-	 * @throws std::runtime_error if the base directory is not a directory or if it is not available
-	 */
-	void open(const std::string & baseDirectory, size_t depth);
+	explicit CFilesystemLoader(const std::string & baseDirectory, size_t depth = 16, bool initial = false);
 
 
 	/**
 	/**
 	 * Loads a resource with the given resource name.
 	 * Loads a resource with the given resource name.
@@ -75,7 +53,7 @@ public:
 	 *
 	 *
 	 * @return a list of all entries in the filesystem.
 	 * @return a list of all entries in the filesystem.
 	 */
 	 */
-	std::list<std::string> getEntries() const;
+	std::unordered_map<ResourceID, std::string> getEntries() const;
 
 
 	/**
 	/**
 	 * Gets the origin of the archive loader.
 	 * Gets the origin of the archive loader.
@@ -84,10 +62,26 @@ public:
 	 */
 	 */
 	std::string getOrigin() const;
 	std::string getOrigin() const;
 
 
+	bool createEntry(std::string filename);
+
 private:
 private:
 	/** The base directory which is scanned and indexed. */
 	/** The base directory which is scanned and indexed. */
 	std::string baseDirectory;
 	std::string baseDirectory;
 
 
-	/** A list of files in the directory */
-	std::list<CFileInfo> fileList;
+	/** A list of files in the directory
+	 * key = ResourceID for resource loader
+	 * value = name that can be used to access file
+	*/
+	std::unordered_map<ResourceID, std::string> fileList;
+
+	/**
+	 * Returns a list of pathnames denoting the files in the directory denoted by this pathname.
+	 *
+	 * If the pathname of this directory is absolute, then the file info pathnames are absolute as well. If the pathname of this directory is relative
+	 * then the file info pathnames are relative to the basedir as well.
+	 *
+	 * @return a list of pathnames denoting the files and directories in the directory denoted by this pathname
+	 * The array will be empty if the directory is empty. Ptr is null if the directory doesn't exist or if it isn't a directory.
+	 */
+	std::unordered_map<ResourceID, std::string> listFiles(size_t depth, bool initial) const;
 };
 };

+ 3 - 23
lib/Filesystem/CLodArchiveLoader.cpp

@@ -13,22 +13,7 @@ ArchiveEntry::ArchiveEntry()
 
 
 }
 }
 
 
-CLodArchiveLoader::CLodArchiveLoader()
-{
-
-}
-
 CLodArchiveLoader::CLodArchiveLoader(const std::string & archive)
 CLodArchiveLoader::CLodArchiveLoader(const std::string & archive)
-{
-	open(archive);
-}
-
-CLodArchiveLoader::CLodArchiveLoader(const CFileInfo & archive)
-{
-	open(archive);
-}
-
-void CLodArchiveLoader::open(const std::string & archive)
 {
 {
 	// Open archive file(.snd, .vid, .lod)
 	// Open archive file(.snd, .vid, .lod)
 	this->archive = archive;
 	this->archive = archive;
@@ -58,11 +43,6 @@ void CLodArchiveLoader::open(const std::string & archive)
 	}
 	}
 }
 }
 
 
-void CLodArchiveLoader::open(const CFileInfo & archive)
-{
-	open(archive.getName());
-}
-
 void CLodArchiveLoader::initLODArchive(CFileInputStream & fileStream)
 void CLodArchiveLoader::initLODArchive(CFileInputStream & fileStream)
 {
 {
 	// Define LodEntryBlock struct
 	// Define LodEntryBlock struct
@@ -204,14 +184,14 @@ std::unique_ptr<CInputStream> CLodArchiveLoader::load(const std::string & resour
 	}
 	}
 }
 }
 
 
-std::list<std::string> CLodArchiveLoader::getEntries() const
+std::unordered_map<ResourceID, std::string> CLodArchiveLoader::getEntries() const
 {
 {
-	std::list<std::string> retList;
+	std::unordered_map<ResourceID, std::string> retList;
 
 
 	for(auto it = entries.begin(); it != entries.end(); ++it)
 	for(auto it = entries.begin(); it != entries.end(); ++it)
 	{
 	{
 		const ArchiveEntry & entry = it->second;
 		const ArchiveEntry & entry = it->second;
-		retList.push_back(entry.name);
+		retList[ResourceID(entry.name)] = entry.name;
 	}
 	}
 
 
 	return std::move(retList);
 	return std::move(retList);

+ 1 - 42
lib/Filesystem/CLodArchiveLoader.h

@@ -45,11 +45,6 @@ struct ArchiveEntry
 class DLL_LINKAGE CLodArchiveLoader : public ISimpleResourceLoader
 class DLL_LINKAGE CLodArchiveLoader : public ISimpleResourceLoader
 {
 {
 public:
 public:
-	/**
-	 * Default c-tor.
-	 */
-	CLodArchiveLoader();
-
 	/**
 	/**
 	 * Ctor.
 	 * Ctor.
 	 *
 	 *
@@ -62,42 +57,6 @@ public:
 	 */
 	 */
 	explicit CLodArchiveLoader(const std::string & archive);
 	explicit CLodArchiveLoader(const std::string & archive);
 
 
-	/**
-	 * Ctor.
-	 *
-	 * The file extension of the param archive determines the type of the Lod file.
-	 * These are valid extensions: .LOD, .SND, .VID
-	 *
-	 * @param archive Specifies the file path to the archive which should be indexed and loaded.
-	 *
-	 * @throws std::runtime_error if the archive wasn't found or if the archive isn't supported
-	 */
-	explicit CLodArchiveLoader(const CFileInfo & archive);
-
-	/**
-	 * Opens an LOD archive.
-	 *
-	 * The file extension of the param archive determines the type of the Lod file.
-	 * These are valid extensions: .LOD, .SND, .VID
-	 *
-	 * @param archive Specifies the file path to the archive which should be indexed and loaded.
-	 *
-	 * @throws std::runtime_error if the archive wasn't found or if the archive isn't supported
-	 */
-	void open(const std::string & archive);
-
-	/**
-	 * Opens an LOD archive.
-	 *
-	 * The file extension of the param archive determines the type of the Lod file.
-	 * These are valid extensions: .LOD, .SND, .VID
-	 *
-	 * @param archive Specifies the file path to the archive which should be indexed and loaded.
-	 *
-	 * @throws std::runtime_error if the archive wasn't found or if the archive isn't supported
-	 */
-	void open(const CFileInfo & archive);
-
 	/**
 	/**
 	 * Loads a resource with the given resource name.
 	 * Loads a resource with the given resource name.
 	 *
 	 *
@@ -113,7 +72,7 @@ public:
 	 *
 	 *
 	 * @return a list of all entries in the archive.
 	 * @return a list of all entries in the archive.
 	 */
 	 */
-	std::list<std::string> getEntries() const;
+	std::unordered_map<ResourceID, std::string> getEntries() const;
 
 
 	/**
 	/**
 	 * Gets the archive entry for the requested resource
 	 * Gets the archive entry for the requested resource

+ 89 - 47
lib/Filesystem/CResourceLoader.cpp

@@ -9,9 +9,6 @@
 #include "../GameConstants.h"
 #include "../GameConstants.h"
 #include "../VCMIDirs.h"
 #include "../VCMIDirs.h"
 
 
-//experimental support for ERA-style mods. Requires custom config in mod directory
-#define ENABLE_ERA_FILESYSTEM
-
 CResourceLoader * CResourceHandler::resourceLoader = nullptr;
 CResourceLoader * CResourceHandler::resourceLoader = nullptr;
 CResourceLoader * CResourceHandler::initialLoader = nullptr;
 CResourceLoader * CResourceHandler::initialLoader = nullptr;
 
 
@@ -35,9 +32,14 @@ ResourceID::ResourceID(const std::string & name, EResType::Type type)
 
 
 ResourceID::ResourceID(const std::string & prefix, const std::string & name, EResType::Type type)
 ResourceID::ResourceID(const std::string & prefix, const std::string & name, EResType::Type type)
 {
 {
-	setName(name);
-	this->name = prefix + this->name;
+	this->name = name;
+
+	size_t dotPos = this->name.find_last_of("/.");
 
 
+	if(dotPos != std::string::npos && this->name[dotPos] == '.')
+		this->name.erase(dotPos);
+
+	this->name = prefix + this->name;
 	setType(type);
 	setType(type);
 }
 }
 
 
@@ -73,15 +75,6 @@ CResourceLoader::CResourceLoader()
 {
 {
 }
 }
 
 
-CResourceLoader::~CResourceLoader()
-{
-	// Delete all loader objects
-	BOOST_FOREACH ( auto & it, loaders)
-	{
-		delete it;
-	}
-}
-
 std::unique_ptr<CInputStream> CResourceLoader::load(const ResourceID & resourceIdent) const
 std::unique_ptr<CInputStream> CResourceLoader::load(const ResourceID & resourceIdent) const
 {
 {
 	auto resource = resources.find(resourceIdent);
 	auto resource = resources.find(resourceIdent);
@@ -139,31 +132,46 @@ std::string CResourceLoader::getResourceName(const ResourceID & resourceIdent) c
 
 
 bool CResourceLoader::existsResource(const ResourceID & resourceIdent) const
 bool CResourceLoader::existsResource(const ResourceID & resourceIdent) const
 {
 {
-	// Check if resource is registered
 	return resources.find(resourceIdent) != resources.end();
 	return resources.find(resourceIdent) != resources.end();
 }
 }
 
 
-void CResourceLoader::addLoader(std::string mountPoint, ISimpleResourceLoader * loader)
+bool CResourceLoader::createResource(std::string URI)
 {
 {
-	loaders.insert(loader);
+	std::string filename = URI;
+	boost::to_upper(URI);
+	BOOST_REVERSE_FOREACH (const LoaderEntry & entry, loaders)
+	{
+		if (entry.writeable && boost::algorithm::starts_with(URI, entry.prefix))
+		{
+			// remove loader prefix from filename
+			filename = filename.substr(entry.prefix.size());
+			if (!entry.loader->createEntry(filename))
+				return false; //or continue loop?
+
+			resources[ResourceID(URI)].push_back(ResourceLocator(entry.loader.get(), filename));
+		}
+	}
+	return false;
+}
+
+void CResourceLoader::addLoader(std::string mountPoint, shared_ptr<ISimpleResourceLoader> loader, bool writeable)
+{
+	LoaderEntry loaderEntry;
+	loaderEntry.loader = loader;
+	loaderEntry.prefix = mountPoint;
+	loaderEntry.writeable = writeable;
+	loaders.push_back(loaderEntry);
 
 
 	// Get entries and add them to the resources list
 	// Get entries and add them to the resources list
-	const std::list<std::string> & entries = loader->getEntries();
+	const std::unordered_map<ResourceID, std::string> & entries = loader->getEntries();
 
 
 	boost::to_upper(mountPoint);
 	boost::to_upper(mountPoint);
 
 
-	BOOST_FOREACH (const std::string & entry, entries)
+	BOOST_FOREACH (auto & entry, entries)
 	{
 	{
-		CFileInfo file(entry);
-
 		// Create identifier and locator and add them to the resources list
 		// Create identifier and locator and add them to the resources list
-		ResourceID ident(mountPoint, file.getStem(), file.getType());
-
-		//check if entry can be directory. Will only work for filesystem loader but H3 archives don't have dirs anyway.
-		if (boost::filesystem::is_directory(loader->getOrigin() + '/' + entry))
-			ident.setType(EResType::DIRECTORY);
-
-		ResourceLocator locator(loader, entry);
+		ResourceID ident(mountPoint, entry.first.getName(), entry.first.getType());
+		ResourceLocator locator(loader.get(), entry.second);
 		resources[ident].push_back(locator);
 		resources[ident].push_back(locator);
 	}
 	}
 }
 }
@@ -178,7 +186,7 @@ CResourceLoader * CResourceHandler::get()
 	{
 	{
 		std::stringstream string;
 		std::stringstream string;
 		string << "Error: Resource loader wasn't initialized. "
 		string << "Error: Resource loader wasn't initialized. "
-			   << "Make sure that you set one via CResourceLoaderFactory::setInstance";
+			   << "Make sure that you set one via CResourceLoaderFactory::initialize";
 		throw std::runtime_error(string.str());
 		throw std::runtime_error(string.str());
 	}
 	}
 }
 }
@@ -234,6 +242,7 @@ EResType::Type EResTypeHelper::getTypeFromExtension(std::string extension)
 	        (".PAC",   EResType::ARCHIVE)
 	        (".PAC",   EResType::ARCHIVE)
 	        (".VID",   EResType::ARCHIVE)
 	        (".VID",   EResType::ARCHIVE)
 	        (".SND",   EResType::ARCHIVE)
 	        (".SND",   EResType::ARCHIVE)
+	        (".PAL",   EResType::PALETTE)
 	        (".VCGM1", EResType::CLIENT_SAVEGAME)
 	        (".VCGM1", EResType::CLIENT_SAVEGAME)
 	        (".VLGM1", EResType::LIB_SAVEGAME)
 	        (".VLGM1", EResType::LIB_SAVEGAME)
 	        (".VSGM1", EResType::SERVER_SAVEGAME);
 	        (".VSGM1", EResType::SERVER_SAVEGAME);
@@ -260,6 +269,7 @@ std::string EResTypeHelper::getEResTypeAsString(EResType::Type type)
 		MAP_ENUM(SOUND)
 		MAP_ENUM(SOUND)
 		MAP_ENUM(MUSIC)
 		MAP_ENUM(MUSIC)
 		MAP_ENUM(ARCHIVE)
 		MAP_ENUM(ARCHIVE)
+		MAP_ENUM(PALETTE)
 		MAP_ENUM(CLIENT_SAVEGAME)
 		MAP_ENUM(CLIENT_SAVEGAME)
 		MAP_ENUM(LIB_SAVEGAME)
 		MAP_ENUM(LIB_SAVEGAME)
 		MAP_ENUM(SERVER_SAVEGAME)
 		MAP_ENUM(SERVER_SAVEGAME)
@@ -276,26 +286,45 @@ std::string EResTypeHelper::getEResTypeAsString(EResType::Type type)
 
 
 void CResourceHandler::initialize()
 void CResourceHandler::initialize()
 {
 {
+	//recurse only into specific directories
+	auto recurseInDir = [](std::string URI, int depth)
+	{
+		auto resources = initialLoader->getResourcesWithName(ResourceID(URI, EResType::DIRECTORY));
+		BOOST_FOREACH(const ResourceLocator & entry, resources)
+		{
+			std::string filename = entry.getLoader()->getOrigin() + '/' + entry.getResourceName();
+			if (!filename.empty())
+			{
+				shared_ptr<ISimpleResourceLoader> dir(new CFilesystemLoader(filename, depth, true));
+				initialLoader->addLoader(URI + '/', dir, false);
+			}
+		}
+	};
+
 	//temporary filesystem that will be used to initialize main one.
 	//temporary filesystem that will be used to initialize main one.
 	//used to solve several case-sensivity issues like Mp3 vs MP3
 	//used to solve several case-sensivity issues like Mp3 vs MP3
 	initialLoader = new CResourceLoader;
 	initialLoader = new CResourceLoader;
 	resourceLoader = new CResourceLoader;
 	resourceLoader = new CResourceLoader;
 
 
-	auto rootDir = new CFilesystemLoader(GameConstants::DATA_DIR, 3);
-	initialLoader->addLoader("GLOBAL/", rootDir);
-	initialLoader->addLoader("ALL/", rootDir);
+	shared_ptr<ISimpleResourceLoader> rootDir(new CFilesystemLoader(GameConstants::DATA_DIR, 0, true));
+	initialLoader->addLoader("GLOBAL/", rootDir, false);
+	initialLoader->addLoader("ALL/", rootDir, false);
 
 
 	auto userDir = rootDir;
 	auto userDir = rootDir;
 
 
 	//add local directory to "ALL" but only if it differs from root dir (true for linux)
 	//add local directory to "ALL" but only if it differs from root dir (true for linux)
 	if (GameConstants::DATA_DIR != GVCMIDirs.UserPath)
 	if (GameConstants::DATA_DIR != GVCMIDirs.UserPath)
 	{
 	{
-		userDir = new CFilesystemLoader(GVCMIDirs.UserPath, 3);
-		initialLoader->addLoader("ALL/", userDir);
+		userDir = shared_ptr<ISimpleResourceLoader>(new CFilesystemLoader(GVCMIDirs.UserPath, 0, true));
+		initialLoader->addLoader("ALL/", userDir, false);
 	}
 	}
 
 
 	//create "LOCAL" dir with current userDir (may be same as rootDir)
 	//create "LOCAL" dir with current userDir (may be same as rootDir)
-	initialLoader->addLoader("LOCAL/", userDir);
+	initialLoader->addLoader("LOCAL/", userDir, false);
+
+	recurseInDir("ALL/CONFIG", 0);// look for configs
+	recurseInDir("ALL/DATA", 0); // look for archives
+	recurseInDir("ALL/MODS", 2); // look for mods. Depth 2 is required for now but won't cause issues if no mods present
 }
 }
 
 
 void CResourceHandler::loadFileSystem(const std::string fsConfigURI)
 void CResourceHandler::loadFileSystem(const std::string fsConfigURI)
@@ -310,23 +339,30 @@ void CResourceHandler::loadFileSystem(const std::string fsConfigURI)
 		{
 		{
 			tlog5 << "loading resource at " << entry["path"].String() << "\n";
 			tlog5 << "loading resource at " << entry["path"].String() << "\n";
 
 
+			std::string URI = entry["path"].String();
 			if (entry["type"].String() == "dir")
 			if (entry["type"].String() == "dir")
 			{
 			{
-				std::string filename = initialLoader->getResourceName(ResourceID(entry["path"].String(), EResType::DIRECTORY));
-				if (!filename.empty())
+				bool writeable = entry["writeable"].Bool();
+				int depth = 16;
+				if (!entry["depth"].isNull())
+					depth = entry["depth"].Float();
+
+				auto resources = initialLoader->getResourcesWithName(ResourceID(URI, EResType::DIRECTORY));
+
+				BOOST_FOREACH(const ResourceLocator & entry, resources)
 				{
 				{
-					int depth = 16;
-					if (!entry["depth"].isNull())
-						depth = entry["depth"].Float();
-					resourceLoader->addLoader(mountPoint.first, new CFilesystemLoader(filename, depth));
+					std::string filename = entry.getLoader()->getOrigin() + '/' + entry.getResourceName();
+					resourceLoader->addLoader(mountPoint.first,
+					    shared_ptr<ISimpleResourceLoader>(new CFilesystemLoader(filename, depth)), writeable);
 				}
 				}
 			}
 			}
 
 
 			if (entry["type"].String() == "file")
 			if (entry["type"].String() == "file")
 			{
 			{
-				std::string filename = initialLoader->getResourceName(ResourceID(entry["path"].String(), EResType::ARCHIVE));
+				std::string filename = initialLoader->getResourceName(ResourceID(URI, EResType::ARCHIVE));
 				if (!filename.empty())
 				if (!filename.empty())
-					resourceLoader->addLoader(mountPoint.first, new CLodArchiveLoader(filename));
+					resourceLoader->addLoader(mountPoint.first,
+					    shared_ptr<ISimpleResourceLoader>(new CLodArchiveLoader(filename)), false);
 			}
 			}
 		}
 		}
 	}
 	}
@@ -334,7 +370,6 @@ void CResourceHandler::loadFileSystem(const std::string fsConfigURI)
 
 
 void CResourceHandler::loadModsFilesystems()
 void CResourceHandler::loadModsFilesystems()
 {
 {
-#ifdef ENABLE_ERA_FILESYSTEM
 
 
 	auto iterator = initialLoader->getIterator([](const ResourceID & ident) ->  bool
 	auto iterator = initialLoader->getIterator([](const ResourceID & ident) ->  bool
 	{
 	{
@@ -346,11 +381,18 @@ void CResourceHandler::loadModsFilesystems()
 		    && boost::algorithm::ends_with(name, "FILESYSTEM");
 		    && boost::algorithm::ends_with(name, "FILESYSTEM");
 	});
 	});
 
 
+	//sorted storage for found mods
+	//implements basic load order (entries in hashtable are basically random)
+	std::set<std::string> foundMods;
 	while (iterator.hasNext())
 	while (iterator.hasNext())
 	{
 	{
-		tlog1 << "Found mod filesystem: " << iterator->getName() << "\n";
-		loadFileSystem(iterator->getName());
+		foundMods.insert(iterator->getName());
 		++iterator;
 		++iterator;
 	}
 	}
-#endif
+
+	BOOST_FOREACH(const std::string & entry, foundMods)
+	{
+		tlog1 << "\t\tFound mod filesystem: " << entry << "\n";
+		loadFileSystem(entry);
+	}
 }
 }

+ 25 - 16
lib/Filesystem/CResourceLoader.h

@@ -33,6 +33,7 @@ class ISimpleResourceLoader;
  * Video: .smk, .bik .mjpg
  * Video: .smk, .bik .mjpg
  * Music: .mp3, .ogg
  * Music: .mp3, .ogg
  * Archive: .lod, .snd, .vid .pac
  * Archive: .lod, .snd, .vid .pac
+ * Palette: .pal
  * Savegame: .v*gm1
  * Savegame: .v*gm1
  */
  */
 namespace EResType
 namespace EResType
@@ -50,6 +51,7 @@ namespace EResType
 		SOUND,
 		SOUND,
 		MUSIC,
 		MUSIC,
 		ARCHIVE,
 		ARCHIVE,
+		PALETTE,
 		CLIENT_SAVEGAME,
 		CLIENT_SAVEGAME,
 		LIB_SAVEGAME,
 		LIB_SAVEGAME,
 		SERVER_SAVEGAME,
 		SERVER_SAVEGAME,
@@ -128,7 +130,7 @@ protected:
 	 * Ctor for usage strictly in resourceLoader for some speedup
 	 * Ctor for usage strictly in resourceLoader for some speedup
 	 *
 	 *
 	 * @param prefix Prefix of ths filename, already in upper case
 	 * @param prefix Prefix of ths filename, already in upper case
-	 * @param name The resource name.
+	 * @param name The resource name, upper case
 	 * @param type The resource type. A constant from the enumeration EResType.
 	 * @param type The resource type. A constant from the enumeration EResType.
 	 */
 	 */
 	ResourceID(const std::string & prefix, const std::string & name, EResType::Type type);
 	ResourceID(const std::string & prefix, const std::string & name, EResType::Type type);
@@ -164,14 +166,14 @@ public:
 		{
 		{
 			return hash<string>()(resourceIdent.getName()) ^ hash<int>()(static_cast<int>(resourceIdent.getType()));
 			return hash<string>()(resourceIdent.getName()) ^ hash<int>()(static_cast<int>(resourceIdent.getType()));
 		}
 		}
-    };
+	};
 };
 };
 
 
 /**
 /**
  * This class manages the loading of resources whether standard
  * This class manages the loading of resources whether standard
  * or derived from several container formats and the file system.
  * or derived from several container formats and the file system.
  */
  */
-class DLL_LINKAGE CResourceLoader : public boost::noncopyable
+class DLL_LINKAGE CResourceLoader
 {
 {
 	typedef std::unordered_map<ResourceID, std::list<ResourceLocator> > ResourcesMap;
 	typedef std::unordered_map<ResourceID, std::list<ResourceLocator> > ResourcesMap;
 
 
@@ -236,11 +238,6 @@ public:
 
 
 	CResourceLoader();
 	CResourceLoader();
 
 
-	/**
-	 * D-tor.
-	 */
-	virtual ~CResourceLoader();
-
 	/**
 	/**
 	 * Loads the resource specified by the resource identifier.
 	 * Loads the resource specified by the resource identifier.
 	 *
 	 *
@@ -290,6 +287,16 @@ public:
 	 */
 	 */
 	bool existsResource(const ResourceID & resourceIdent) const;
 	bool existsResource(const ResourceID & resourceIdent) const;
 
 
+	/**
+	 * Creates new resource (if not exists) with specified URI.
+	 * Type will be determined from extension
+	 * File case will be same as in URI
+	 *
+	 * @param URI file to create
+	 * @return true on success, false if resource exists or on error
+	 */
+	bool createResource(std::string URI);
+
 	/**
 	/**
 	 * Adds a simple resource loader to the loaders list and its entries to the resources list.
 	 * Adds a simple resource loader to the loaders list and its entries to the resources list.
 	 *
 	 *
@@ -300,7 +307,7 @@ public:
 	 * @param mountPoint prefix that will be added to all files in this loader
 	 * @param mountPoint prefix that will be added to all files in this loader
 	 * @param loader The simple resource loader object to add
 	 * @param loader The simple resource loader object to add
 	 */
 	 */
-	void addLoader(std::string mountPoint, ISimpleResourceLoader * loader);
+	void addLoader(std::string mountPoint, shared_ptr<ISimpleResourceLoader> loader, bool writeable);
 
 
 private:
 private:
 
 
@@ -310,19 +317,21 @@ private:
 	 */
 	 */
 	ResourcesMap resources;
 	ResourcesMap resources;
 
 
+	struct LoaderEntry
+	{
+		std::string prefix;
+		shared_ptr<ISimpleResourceLoader> loader;
+		bool writeable;
+	};
+
 	/** A list of resource loader objects */
 	/** A list of resource loader objects */
-	std::set<ISimpleResourceLoader *> loaders;
+	std::vector<LoaderEntry > loaders;
 };
 };
 
 
 /**
 /**
  * This class has static methods for a global resource loader access.
  * This class has static methods for a global resource loader access.
  *
  *
- * Note: Compared to the singleton pattern it has the advantage that the class CResourceLoader
- * and classes which use it can be tested separately. CResourceLoader can be sub-classes as well.
- * Compared to a global variable the factory pattern can throw an exception if the resource loader wasn't
- * initialized.
- *
- * This class is not thread-safe. Make sure nobody is calling getInstance while somebody else is calling setInstance.
+ * Class is not thread-safe. Make sure nobody is calling getInstance while somebody else is calling initialize.
  */
  */
 class DLL_LINKAGE CResourceHandler
 class DLL_LINKAGE CResourceHandler
 {
 {

+ 12 - 1
lib/Filesystem/ISimpleResourceLoader.h

@@ -12,6 +12,7 @@
 #pragma once
 #pragma once
 
 
 #include "CInputStream.h"
 #include "CInputStream.h"
+#include "CResourceLoader.h" //FIXME: move ResourceID + EResType in separate file?
 
 
 /**
 /**
  * A class which knows the files containing in the archive or system and how to load them.
  * A class which knows the files containing in the archive or system and how to load them.
@@ -44,7 +45,7 @@ public:
 	 *
 	 *
 	 * @return Returns a list of all entries in the archive or (file) system.
 	 * @return Returns a list of all entries in the archive or (file) system.
 	 */
 	 */
-	virtual std::list<std::string> getEntries() const =0;
+	virtual std::unordered_map<ResourceID, std::string> getEntries() const =0;
 
 
 	/**
 	/**
 	 * Gets the origin of the loader.
 	 * Gets the origin of the loader.
@@ -52,4 +53,14 @@ public:
 	 * @return the file path to source of this loader
 	 * @return the file path to source of this loader
 	 */
 	 */
 	virtual std::string getOrigin() const =0;
 	virtual std::string getOrigin() const =0;
+
+	/**
+	 * Creates new resource with specified filename.
+	 *
+	 * @returns true if new file was created, false on error or if file already exists
+	 */
+	virtual bool createEntry(std::string filename)
+	{
+		return false;
+	}
 };
 };

+ 9 - 2
lib/VCMI_Lib.cpp

@@ -160,12 +160,19 @@ DLL_LINKAGE void loadToIt(si32 &dest, const std::string &src, int &iter, int mod
 
 
 void LibClasses::loadFilesystem()
 void LibClasses::loadFilesystem()
 {
 {
-	CStopWatch pomtime;
+	CStopWatch totalTime;
+	CStopWatch loadTime;
+
 	CResourceHandler::initialize();
 	CResourceHandler::initialize();
+	tlog0<<"\t Initialization: "<<loadTime.getDiff()<<std::endl;
+
 	CResourceHandler::loadFileSystem("ALL/config/filesystem.json");
 	CResourceHandler::loadFileSystem("ALL/config/filesystem.json");
+	tlog0<<"\t Data loading: "<<loadTime.getDiff()<<std::endl;
+
 	CResourceHandler::loadModsFilesystems();
 	CResourceHandler::loadModsFilesystems();
+	tlog0<<"\t Mod filesystems: "<<loadTime.getDiff()<<std::endl;
 
 
-	tlog0<<"\tFile system handler: "<<pomtime.getDiff()<<std::endl;
+	tlog0<<"File system handler: "<<totalTime.getDiff()<<std::endl;
 }
 }
 
 
 void LibClasses::init()
 void LibClasses::init()

+ 11 - 5
server/CGameHandler.cpp

@@ -1,5 +1,7 @@
 #include "StdInc.h"
 #include "StdInc.h"
 
 
+#include "../lib/Filesystem/CResourceLoader.h"
+#include "../lib/Filesystem/CFileInfo.h"
 #include "../lib/int3.h"
 #include "../lib/int3.h"
 #include "../lib/CCampaignHandler.h"
 #include "../lib/CCampaignHandler.h"
 #include "../lib/StartInfo.h"
 #include "../lib/StartInfo.h"
@@ -2247,12 +2249,16 @@ void CGameHandler::sendAndApply( NewStructures * info )
 		checkLossVictory(getTown(info->tid)->tempOwner);
 		checkLossVictory(getTown(info->tid)->tempOwner);
 }
 }
 
 
-void CGameHandler::save( const std::string &fname )
+void CGameHandler::save(const std::string & filename )
 {
 {
+	tlog1 << "Saving to " << filename << "\n";
+	CFileInfo info(filename);
+	CResourceHandler::get()->createResource(info.getStem() + ".vlgm1");
+	CResourceHandler::get()->createResource(info.getStem() + ".vsgm1");
+
 	{
 	{
 		tlog0 << "Ordering clients to serialize...\n";
 		tlog0 << "Ordering clients to serialize...\n";
-		SaveGame sg(fname);
-
+		SaveGame sg(info.getStem() + ".vcgm1");
 		sendToAllClients(&sg);
 		sendToAllClients(&sg);
 	}
 	}
 
 
@@ -2260,14 +2266,14 @@ void CGameHandler::save( const std::string &fname )
 	{
 	{
 		{
 		{
 			tlog0 << "Serializing game info...\n";
 			tlog0 << "Serializing game info...\n";
-			CSaveFile save(GVCMIDirs.UserPath + "/Games/" + fname + ".vlgm1");
+			CSaveFile save(CResourceHandler::get()->getResourceName(ResourceID(info.getStem(), EResType::LIB_SAVEGAME)));
 			char hlp[8] = "VCMISVG";
 			char hlp[8] = "VCMISVG";
 			save << hlp << static_cast<CMapHeader&>(*gs->map) << gs->scenarioOps << *VLC << gs;
 			save << hlp << static_cast<CMapHeader&>(*gs->map) << gs->scenarioOps << *VLC << gs;
 		}
 		}
 
 
 		{
 		{
 			tlog0 << "Serializing server info...\n";
 			tlog0 << "Serializing server info...\n";
-			CSaveFile save(GVCMIDirs.UserPath + "/Games/" + fname + ".vsgm1");
+			CSaveFile save(CResourceHandler::get()->getResourceName(ResourceID(info.getStem(), EResType::SERVER_SAVEGAME)));
 			save << *this;
 			save << *this;
 		}
 		}
 		tlog0 << "Game has been successfully saved!\n";
 		tlog0 << "Game has been successfully saved!\n";