Browse Source

Filesystem handling is now more flexible
- removed CResourceLoader class in favor of one that implements resource loader interface
- removed global pool of files, in favour of more dynamic approach
- renamed some files to match current situation
All these changes are needed mostly for future mod manager + .zip support

Ivan Savenko 12 năm trước cách đây
mục cha
commit
b87897096c
64 tập tin đã thay đổi với 1192 bổ sung1510 xóa
  1. 1 1
      client/AdventureMapClasses.cpp
  2. 7 7
      client/CAnimation.cpp
  3. 3 5
      client/CBitmapHandler.cpp
  4. 4 3
      client/CDefHandler.cpp
  5. 10 11
      client/CMT.cpp
  6. 4 4
      client/CMusicHandler.cpp
  7. 17 22
      client/CPreGame.cpp
  8. 5 5
      client/CPreGame.h
  9. 1 1
      client/CVideoHandler.cpp
  10. 2 2
      client/Client.cpp
  11. 2 2
      client/Graphics.cpp
  12. 2 2
      client/NetPacksClient.cpp
  13. 2 2
      client/battle/CBattleInterface.cpp
  14. 4 3
      client/battle/CCreatureAnimation.cpp
  15. 3 3
      client/gui/Fonts.cpp
  16. 20 27
      config/filesystem.json
  17. 1 1
      lib/BattleState.cpp
  18. 1 1
      lib/CArtHandler.cpp
  19. 1 3
      lib/CBonusTypeHandler.cpp
  20. 2 2
      lib/CConfigHandler.cpp
  21. 1 1
      lib/CCreatureHandler.cpp
  22. 3 3
      lib/CDefObjInfoHandler.cpp
  23. 1 1
      lib/CGameState.cpp
  24. 2 3
      lib/CGeneralTextHandler.cpp
  25. 1 1
      lib/CHeroHandler.cpp
  26. 10 6
      lib/CMakeLists.txt
  27. 2 3
      lib/CModHandler.cpp
  28. 1 1
      lib/CModHandler.h
  29. 1 1
      lib/CObjectHandler.cpp
  30. 1 1
      lib/CSpellHandler.cpp
  31. 1 1
      lib/CTownHandler.cpp
  32. 5 9
      lib/JsonNode.cpp
  33. 6 6
      lib/VCMI_Lib.cpp
  34. 138 0
      lib/filesystem/AdapterLoaders.cpp
  35. 79 0
      lib/filesystem/AdapterLoaders.h
  36. 181 0
      lib/filesystem/CArchiveLoader.cpp
  37. 17 17
      lib/filesystem/CArchiveLoader.h
  38. 1 5
      lib/filesystem/CBinaryReader.cpp
  39. 1 2
      lib/filesystem/CBinaryReader.h
  40. 0 2
      lib/filesystem/CCompressedStream.cpp
  41. 1 2
      lib/filesystem/CCompressedStream.h
  42. 2 3
      lib/filesystem/CFileInfo.h
  43. 1 1
      lib/filesystem/CFileInputStream.cpp
  44. 1 2
      lib/filesystem/CFileInputStream.h
  45. 46 25
      lib/filesystem/CFilesystemLoader.cpp
  46. 12 10
      lib/filesystem/CFilesystemLoader.h
  47. 13 4
      lib/filesystem/CInputStream.h
  48. 0 220
      lib/filesystem/CLodArchiveLoader.cpp
  49. 0 45
      lib/filesystem/CMappedFileLoader.cpp
  50. 0 53
      lib/filesystem/CMappedFileLoader.h
  51. 1 2
      lib/filesystem/CMemoryStream.h
  52. 0 491
      lib/filesystem/CResourceLoader.cpp
  53. 0 446
      lib/filesystem/CResourceLoader.h
  54. 316 0
      lib/filesystem/Filesystem.cpp
  55. 208 0
      lib/filesystem/Filesystem.h
  56. 33 22
      lib/filesystem/ISimpleResourceLoader.h
  57. 1 1
      lib/mapping/CCampaignHandler.cpp
  58. 1 1
      lib/mapping/CMapEditManager.cpp
  59. 1 1
      lib/mapping/CMapService.cpp
  60. 1 2
      lib/mapping/MapFormatH3M.cpp
  61. 1 1
      lib/rmg/CMapGenerator.cpp
  62. 3 3
      server/CGameHandler.cpp
  63. 2 2
      server/CVCMIServer.cpp
  64. 4 3
      test/CMapEditManagerTest.cpp

+ 1 - 1
client/AdventureMapClasses.cpp

@@ -3,7 +3,7 @@
 
 #include "../CCallback.h"
 #include "../lib/JsonNode.h"
-#include "../lib/filesystem/CResourceLoader.h"
+#include "../lib/filesystem/Filesystem.h"
 #include "../lib/mapping/CMap.h"
 #include "../lib/CModHandler.h"
 #include "../lib/CObjectHandler.h"

+ 7 - 7
client/CAnimation.cpp

@@ -1,7 +1,7 @@
 #include "StdInc.h"
 #include <SDL_image.h>
 
-#include "../lib/filesystem/CResourceLoader.h"
+#include "../lib/filesystem/Filesystem.h"
 #include "../lib/filesystem/ISimpleResourceLoader.h"
 #include "../lib/JsonNode.h"
 
@@ -107,7 +107,7 @@ public:
 			cache.pop_front();
 		cache.push_back(FileData());
 
-		auto data =  CResourceHandler::get()->loadData(rid);
+		auto data =  CResourceHandler::get()->load(rid)->readAll();
 		cache.back().name = ResourceID(rid);
 		cache.back().size = data.second;
 		cache.back().data = data.first.release();
@@ -1023,8 +1023,8 @@ void CAnimation::init(CDefFile * file)
 	{
 		const std::map<size_t, size_t> defEntries = file->getEntries();
 
-		for (auto & defEntrie : defEntries)
-			source[defEntrie.first].resize(defEntrie.second);
+		for (auto & defEntry : defEntries)
+			source[defEntry.first].resize(defEntry.second);
 	}
 
 	ResourceID resID(std::string("SPRITES/") + name, EResType::TEXT);
@@ -1032,11 +1032,11 @@ void CAnimation::init(CDefFile * file)
 	if (vstd::contains(graphics->imageLists, resID.getName()))
 		initFromJson(graphics->imageLists[resID.getName()]);
 
-	auto & configList = CResourceHandler::get()->getResourcesWithName(resID);
+	auto configList = CResourceHandler::get()->getResourcesWithName(resID);
 
-	for(auto & entry : configList)
+	for(auto & loader : configList)
 	{
-		auto stream = entry.getLoader()->load(entry.getResourceName());
+		auto stream = loader->load(resID);
 		std::unique_ptr<ui8[]> textData(new ui8[stream->getSize()]);
 		stream->read(textData.get(), stream->getSize());
 

+ 3 - 5
client/CBitmapHandler.cpp

@@ -1,6 +1,6 @@
 #include "StdInc.h"
 
-#include "../lib/filesystem/CResourceLoader.h"
+#include "../lib/filesystem/Filesystem.h"
 #include "../lib/filesystem/CFileInfo.h"
 #include "SDL.h"
 #include "SDL_image.h"
@@ -112,10 +112,8 @@ SDL_Surface * BitmapHandler::loadBitmapFromDir(std::string path, std::string fna
 
 	SDL_Surface * ret=nullptr;
 
-	auto readFile = CResourceHandler::get()->loadData(
-	                              ResourceID(path + fname, EResType::IMAGE));
+	auto readFile = CResourceHandler::get()->load(ResourceID(path + fname, EResType::IMAGE))->readAll();
 
-	
 	if (isPCX(readFile.first.get()))
 	{//H3-style PCX
 		ret = loadH3PCX(readFile.first.get(), readFile.second);
@@ -132,7 +130,7 @@ SDL_Surface * BitmapHandler::loadBitmapFromDir(std::string path, std::string fna
 	}
 	else
 	{ //loading via SDL_Image
-		CFileInfo info(CResourceHandler::get()->getResourceName(ResourceID(path + fname, EResType::IMAGE)));
+		CFileInfo info(*CResourceHandler::get()->getResourceName(ResourceID(path + fname, EResType::IMAGE)));
 
 		ret = IMG_LoadTyped_RW(
 		          //create SDL_RW with our data (will be deleted by SDL)

+ 4 - 3
client/CDefHandler.cpp

@@ -2,7 +2,7 @@
 #include "SDL.h"
 #include "CDefHandler.h"
 
-#include "../lib/filesystem/CResourceLoader.h"
+#include "../lib/filesystem/Filesystem.h"
 #include "../lib/VCMI_Lib.h"
 #include "CBitmapHandler.h"
 
@@ -362,8 +362,9 @@ CDefEssential * CDefHandler::essentialize()
 
 CDefHandler * CDefHandler::giveDef(const std::string & defName)
 {
-	ui8 * data = CResourceHandler::get()->loadData(
-	                 ResourceID(std::string("SPRITES/") + defName, EResType::ANIMATION)).first.release();
+	ResourceID resID(std::string("SPRITES/") + defName, EResType::ANIMATION);
+
+	ui8 * data = CResourceHandler::get()->load(resID)->readAll().first.release();
 	if(!data)
 		throw std::runtime_error("bad def name!");
 	auto   nh = new CDefHandler();

+ 10 - 11
client/CMT.cpp

@@ -6,7 +6,7 @@
 #include "CGameInfo.h"
 #include "mapHandler.h"
 
-#include "../lib/filesystem/CResourceLoader.h"
+#include "../lib/filesystem/Filesystem.h"
 #include "CPreGame.h"
 #include "CCastleInterface.h"
 #include "../lib/CConsoleHandler.h"
@@ -300,8 +300,8 @@ int main(int argc, char** argv)
 		return false;
 	};
 
-	if (!testFile("DATA/HELP.TXT", "Heroes III data") &&
-	    !testFile("MODS/VCMI/MOD.JSON", "VCMI mod") &&
+	if (!testFile("DATA/HELP.TXT", "Heroes III data") ||
+	    !testFile("MODS/VCMI/MOD.JSON", "VCMI mod") ||
 	    !testFile("DATA/StackQueueBgBig.PCX", "VCMI data"))
 		exit(1); // These are unrecoverable errors
 
@@ -536,22 +536,21 @@ void processCommand(const std::string &message)
 
 		std::string outPath = VCMIDirs::get().userCachePath() + "/extracted/";
 
-		auto iterator = CResourceHandler::get()->getIterator([](const ResourceID & ident)
+		auto list = CResourceHandler::get()->getFilteredFiles([](const ResourceID & ident)
 		{
 			return ident.getType() == EResType::TEXT && boost::algorithm::starts_with(ident.getName(), "DATA/");
 		});
 
-		while (iterator.hasNext())
+		for (auto & filename : list)
 		{
-			std::string outName = outPath + iterator->getName();
+			std::string outName = outPath + filename.getName();
 
 			boost::filesystem::create_directories(outName.substr(0, outName.find_last_of("/")));
 
 			std::ofstream file(outName + ".TXT");
-			auto text = CResourceHandler::get()->loadData(*iterator);
+			auto text = CResourceHandler::get()->load(filename)->readAll();
 
 			file.write((char*)text.first.get(), text.second);
-			++iterator;
 		}
 
         std::cout << "\rExtracting done :)\n";
@@ -661,7 +660,7 @@ void processCommand(const std::string &message)
 		{
 			CDefEssential * cde = CDefHandler::giveDefEss(URI);
 
-			std::string outName = CResourceHandler::get()->getResource(ResourceID("SPRITES/" + URI)).getResourceName();
+			std::string outName = *CResourceHandler::get()->getResourceName(ResourceID("SPRITES/" + URI));
 			std::string outPath = VCMIDirs::get().userCachePath() + "/extracted/";
 
 			boost::filesystem::create_directories(outPath + outName);
@@ -682,11 +681,11 @@ void processCommand(const std::string &message)
 
 		if (CResourceHandler::get()->existsResource(ResourceID(URI)))
 		{
-			std::string outName = CResourceHandler::get()->getResource(ResourceID(URI)).getResourceName();
+			std::string outName = *CResourceHandler::get()->getResourceName(ResourceID(URI));
 			std::string outPath = VCMIDirs::get().userCachePath() + "/extracted/";
 			std::string fullPath = outPath + outName;
 
-			auto data = CResourceHandler::get()->loadData(ResourceID(URI));
+			auto data = CResourceHandler::get()->load(ResourceID(URI))->readAll();
 
 			boost::filesystem::create_directories(fullPath.substr(0, fullPath.find_last_of("/")));
 			std::ofstream outFile(outPath + outName);

+ 4 - 4
client/CMusicHandler.cpp

@@ -7,7 +7,7 @@
 #include "../client/CGameInfo.h"
 #include "../lib/JsonNode.h"
 #include "../lib/GameConstants.h"
-#include "../lib/filesystem/CResourceLoader.h"
+#include "../lib/filesystem/Filesystem.h"
 
 /*
  * CMusicHandler.cpp, part of VCMI engine
@@ -139,7 +139,7 @@ Mix_Chunk *CSoundHandler::GetSoundChunk(soundBase::soundID soundID)
 	// Load and insert
 	try
 	{
-		auto data = CResourceHandler::get()->loadData(ResourceID(std::string("SOUNDS/") + fname, EResType::SOUND));
+		auto data = CResourceHandler::get()->load(ResourceID(std::string("SOUNDS/") + fname, EResType::SOUND))->readAll();
 
 		SDL_RWops *ops = SDL_RWFromMem(data.first.release(), data.second);
 		Mix_Chunk *chunk;
@@ -162,7 +162,7 @@ Mix_Chunk *CSoundHandler::GetSoundChunk(std::string &sound)
 	// Load and insert
 	try
 	{
-		auto data = CResourceHandler::get()->loadData(ResourceID(std::string("SOUNDS/") + sound, EResType::SOUND)); //TODO: allow other sound folders?
+		auto data = CResourceHandler::get()->load(ResourceID(std::string("SOUNDS/") + sound, EResType::SOUND))->readAll();
 
 		SDL_RWops *ops = SDL_RWFromMem(data.first.release(), data.second);
 		Mix_Chunk *chunk;
@@ -477,7 +477,7 @@ void MusicEntry::load(std::string musicURI)
 
     logGlobal->traceStream()<<"Loading music file "<<musicURI;
 
-	music = Mix_LoadMUS(CResourceHandler::get()->getResourceName(ResourceID(musicURI, EResType::MUSIC)).c_str());
+	music = Mix_LoadMUS(CResourceHandler::get()->getResourceName(ResourceID(musicURI, EResType::MUSIC))->c_str());
 
 	if(!music)
 	{

+ 17 - 22
client/CPreGame.cpp

@@ -1,7 +1,7 @@
 #include "StdInc.h"
 #include "CPreGame.h"
 
-#include "../lib/filesystem/CResourceLoader.h"
+#include "../lib/filesystem/Filesystem.h"
 #include "../lib/filesystem/CFileInfo.h"
 #include "../lib/filesystem/CCompressedStream.h"
 
@@ -403,7 +403,7 @@ CreditsScreen::CreditsScreen()
 	OBJ_CONSTRUCTION_CAPTURING_ALL;
 	pos.w = CGP->menu->pos.w;
 	pos.h = CGP->menu->pos.h;
-	auto textFile = CResourceHandler::get()->loadData(ResourceID("DATA/CREDITS.TXT"));
+	auto textFile = CResourceHandler::get()->load(ResourceID("DATA/CREDITS.TXT"))->readAll();
 	std::string text((char*)textFile.first.get(), textFile.second);
 	size_t firstQuote = text.find('\"')+1;
 	text = text.substr(firstQuote, text.find('\"', firstQuote) - firstQuote );
@@ -1090,27 +1090,20 @@ void SelectionTab::filter( int size, bool selectFirst )
 	}
 }
 
-std::vector<ResourceID> SelectionTab::getFiles(std::string dirURI, int resType)
+std::unordered_set<ResourceID> SelectionTab::getFiles(std::string dirURI, int resType)
 {
-	std::vector<ResourceID> ret;
 	boost::to_upper(dirURI);
 
-	auto iterator = CResourceHandler::get()->getIterator([&](const ResourceID & ident)
+	std::unordered_set<ResourceID> ret = CResourceHandler::get()->getFilteredFiles([&](const ResourceID & ident)
 	{
 		return ident.getType() == resType
 			&& boost::algorithm::starts_with(ident.getName(), dirURI);
 	});
 
-	while (iterator.hasNext())
-	{
-		ret.push_back(*iterator);
-		++iterator;
-	}
-
 	return ret;
 }
 
-void SelectionTab::parseMaps(const std::vector<ResourceID> & files)
+void SelectionTab::parseMaps(const std::unordered_set<ResourceID> &files)
 {
 	allItems.clear();
 	for(auto & file : files)
@@ -1131,13 +1124,13 @@ void SelectionTab::parseMaps(const std::vector<ResourceID> & files)
 	}
 }
 
-void SelectionTab::parseGames(const std::vector<ResourceID> &files, bool multi)
+void SelectionTab::parseGames(const std::unordered_set<ResourceID> &files, bool multi)
 {
 	for(auto & file : files)
 	{
 		try
 		{
-			CLoadFile lf(CResourceHandler::get()->getResourceName(file));
+			CLoadFile lf(*CResourceHandler::get()->getResourceName(file));
 			lf.checkMagicBytes(SAVEGAME_MAGIC);
 // 			ui8 sign[8];
 // 			lf >> sign;
@@ -1153,7 +1146,7 @@ void SelectionTab::parseGames(const std::vector<ResourceID> &files, bool multi)
 			lf >> *(mapInfo.mapHeader.get()) >> mapInfo.scenarioOpts;
 			mapInfo.fileURI = file.getName();
 			mapInfo.countPlayers();
-			std::time_t time = CFileInfo(CResourceHandler::get()->getResourceName(file)).getDate();
+			std::time_t time = CFileInfo(*CResourceHandler::get()->getResourceName(file)).getDate();
 			mapInfo.date = std::asctime(std::localtime(&time));
 
 			// If multi mode then only multi games, otherwise single
@@ -1171,14 +1164,16 @@ void SelectionTab::parseGames(const std::vector<ResourceID> &files, bool multi)
 	}
 }
 
-void SelectionTab::parseCampaigns(const std::vector<ResourceID> & files )
+void SelectionTab::parseCampaigns(const std::unordered_set<ResourceID> &files )
 {
-	allItems.resize(files.size());
-	for(int i=0; i<files.size(); i++)
+	allItems.reserve(files.size());
+	for (auto & file : files)
 	{
+		CMapInfo info;
 		//allItems[i].date = std::asctime(std::localtime(&files[i].date));
-		allItems[i].fileURI = files[i].getName();
-		allItems[i].campaignInit();
+		info.fileURI = file.getName();
+		info.campaignInit();
+		allItems.push_back(std::move(info));
 	}
 }
 
@@ -1356,7 +1351,7 @@ void SelectionTab::select( int position )
 
 	if(txt)
 	{
-		std::string filename = CResourceHandler::get()->getResourceName(
+		std::string filename = *CResourceHandler::get()->getResourceName(
 								   ResourceID(curItems[py]->fileURI, EResType::CLIENT_SAVEGAME));
 		txt->setTxt(CFileInfo(filename).getBaseName());
 	}
@@ -1489,7 +1484,7 @@ void SelectionTab::printMaps(SDL_Surface *to)
 		}
 		else
 		{
-			name = CFileInfo(CResourceHandler::get()->getResourceName(
+			name = CFileInfo(*CResourceHandler::get()->getResourceName(
 								 ResourceID(currentItem->fileURI, EResType::CLIENT_SAVEGAME))).getBaseName();
 		}
 

+ 5 - 5
client/CPreGame.h

@@ -1,6 +1,6 @@
 #pragma once
 
-#include "../lib/filesystem/CResourceLoader.h"
+#include "../lib/filesystem/Filesystem.h"
 #include <SDL.h>
 #include "../lib/StartInfo.h"
 #include "GUIClasses.h"
@@ -145,10 +145,10 @@ class SelectionTab : public CIntObject
 private:
 	CDefHandler *format; //map size
 
-    void parseMaps(const std::vector<ResourceID> &files);
-	void parseGames(const std::vector<ResourceID> &files, bool multi);
-	void parseCampaigns(const std::vector<ResourceID> & files );
-	std::vector<ResourceID> getFiles(std::string dirURI, int resType);
+    void parseMaps(const std::unordered_set<ResourceID> &files);
+	void parseGames(const std::unordered_set<ResourceID> &files, bool multi);
+	void parseCampaigns(const std::unordered_set<ResourceID> & files );
+	std::unordered_set<ResourceID> getFiles(std::string dirURI, int resType);
 	CMenuScreen::EState tabType;
 public:
 	int positions; //how many entries (games/maps) can be shown

+ 1 - 1
client/CVideoHandler.cpp

@@ -5,7 +5,7 @@
 #include "gui/CGuiHandler.h"
 #include "gui/SDL_Extensions.h"
 #include "CPlayerInterface.h"
-#include "../lib/filesystem/CResourceLoader.h"
+#include "../lib/filesystem/Filesystem.h"
 
 extern CGuiHandler GH; //global gui handler
 

+ 2 - 2
client/Client.cpp

@@ -226,12 +226,12 @@ void CClient::loadGame( const std::string & fname )
 	CStopWatch tmh;
 	try
 	{
-		std::string clientSaveName = CResourceHandler::get()->getResourceName(ResourceID(fname, EResType::CLIENT_SAVEGAME));
+		std::string clientSaveName = *CResourceHandler::get()->getResourceName(ResourceID(fname, EResType::CLIENT_SAVEGAME));
 		std::string controlServerSaveName;
 
 		if (CResourceHandler::get()->existsResource(ResourceID(fname, EResType::SERVER_SAVEGAME)))
 		{
-			controlServerSaveName = CResourceHandler::get()->getResourceName(ResourceID(fname, EResType::SERVER_SAVEGAME));
+			controlServerSaveName = *CResourceHandler::get()->getResourceName(ResourceID(fname, EResType::SERVER_SAVEGAME));
 		}
 		else// create entry for server savegame. Triggered if save was made after launch and not yet present in res handler
 		{

+ 2 - 2
client/Graphics.cpp

@@ -1,7 +1,7 @@
 #include "StdInc.h"
 #include "Graphics.h"
 
-#include "../lib/filesystem/CResourceLoader.h"
+#include "../lib/filesystem/Filesystem.h"
 #include "../lib/filesystem/CBinaryReader.h"
 #include "CDefHandler.h"
 #include "gui/SDL_Extensions.h"
@@ -47,7 +47,7 @@ Graphics * graphics = nullptr;
 
 void Graphics::loadPaletteAndColors()
 {
-	auto textFile = CResourceHandler::get()->loadData(ResourceID("DATA/PLAYERS.PAL"));
+	auto textFile = CResourceHandler::get()->load(ResourceID("DATA/PLAYERS.PAL"))->readAll();
 	std::string pals((char*)textFile.first.get(), textFile.second);
 
 	playerColorPalette = new SDL_Color[256];

+ 2 - 2
client/NetPacksClient.cpp

@@ -1,7 +1,7 @@
 #include "StdInc.h"
 #include "../lib/NetPacks.h"
 
-#include "../lib/filesystem/CResourceLoader.h"
+#include "../lib/filesystem/Filesystem.h"
 #include "../lib/filesystem/CFileInfo.h"
 #include "../CCallback.h"
 #include "Client.h"
@@ -796,7 +796,7 @@ void SaveGame::applyCl(CClient *cl)
 
 	try
 	{
-		CSaveFile save(CResourceHandler::get()->getResourceName(ResourceID(info.getStem(), EResType::CLIENT_SAVEGAME)));
+		CSaveFile save(*CResourceHandler::get()->getResourceName(ResourceID(info.getStem(), EResType::CLIENT_SAVEGAME)));
 		cl->saveCommonState(save);
 		save << *cl;
 	}

+ 2 - 2
client/battle/CBattleInterface.cpp

@@ -975,7 +975,7 @@ void CBattleInterface::newStack(const CStack * stack)
 
 		creAnims[stack->ID] = AnimationControls::getAnimation(turretCreature);
 
-		// Turret positions are read out of the /config/wall_pos.txt
+		// Turret positions are read out of the config/wall_pos.txt
 		int posID = 0;
 		switch (stack->position)
 		{
@@ -2184,7 +2184,7 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
 				break;
 			case FRIENDLY_CREATURE_SPELL:
 			{
-				if (isCastingPossibleHere (sactive, shere, myNumber)); //need to be called before sp is determined
+				if (isCastingPossibleHere (sactive, shere, myNumber)) //need to be called before sp is determined
 				{
 					bool rise = false; //TODO: can you imagine rising hostile creatures?
 					sp = CGI->spellh->spells[creatureCasting ? creatureSpellToCast : spellToCast->additionalInfo];

+ 4 - 3
client/battle/CCreatureAnimation.cpp

@@ -7,7 +7,7 @@
 #include "../gui/SDL_Extensions.h"
 #include "../gui/SDL_Pixels.h"
 
-#include "../../lib/filesystem/CResourceLoader.h"
+#include "../../lib/filesystem/Filesystem.h"
 #include "../../lib/filesystem/CBinaryReader.h"
 #include "../../lib/filesystem/CMemoryStream.h"
 
@@ -155,8 +155,9 @@ CCreatureAnimation::CCreatureAnimation(std::string name, TSpeedController contro
 {
 	// separate block to avoid accidental use of "data" after it was moved into "pixelData"
 	{
-		auto data = CResourceHandler::get()->loadData(
-				   ResourceID(std::string("SPRITES/") + name, EResType::ANIMATION));
+		ResourceID resID(std::string("SPRITES/") + name, EResType::ANIMATION);
+
+		auto data = CResourceHandler::get()->load(resID)->readAll();
 
 		pixelData = std::move(data.first);
 		pixelDataSize = data.second;

+ 3 - 3
client/gui/Fonts.cpp

@@ -6,7 +6,7 @@
 #include "SDL_Pixels.h"
 #include "../../lib/JsonNode.h"
 #include "../../lib/vcmi_endian.h"
-#include "../../lib/filesystem/CResourceLoader.h"
+#include "../../lib/filesystem/Filesystem.h"
 
 /*
  * Fonts.cpp, part of VCMI engine
@@ -95,7 +95,7 @@ std::array<CBitmapFont::Char, CBitmapFont::totalChars> CBitmapFont::loadChars()
 }
 
 CBitmapFont::CBitmapFont(const std::string & filename):
-    data(CResourceHandler::get()->loadData(ResourceID("data/" + filename, EResType::BMP_FONT))),
+    data(CResourceHandler::get()->load(ResourceID("data/" + filename, EResType::BMP_FONT))->readAll()),
     chars(loadChars()),
     height(data.first.get()[5])
 {}
@@ -198,7 +198,7 @@ void CBitmapFont::renderText(SDL_Surface * surface, const std::string & data, co
 std::pair<std::unique_ptr<ui8[]>, ui64> CTrueTypeFont::loadData(const JsonNode & config)
 {
 	std::string filename = "Data/" + config["file"].String();
-	return CResourceHandler::get()->loadData(ResourceID(filename, EResType::TTF_FONT));
+	return CResourceHandler::get()->load(ResourceID(filename, EResType::TTF_FONT))->readAll();
 }
 
 TTF_Font * CTrueTypeFont::loadFont(const JsonNode &config)

+ 20 - 27
config/filesystem.json

@@ -2,59 +2,52 @@
 	// Complete filesystem available after initialization
 	// All paths and names here are case-insensitive
 	// If same filename is found twice entry from latest source will be used
-	// path can start from:
-	// "GLOBAL" - global location for data files. /usr/share/vcmi | C:\Program files\Heroes 3\
-	// "LOCAL" - local user-specific files. ~/.vcmi | same as global (TODO: move it to C:\Users\whatever)
-	// "ALL" - will check local directory first or (if was not found) in global directory
-	// NOTE: this file must be available as "ALL/config/filesystem.json"
+	// NOTES:
+	// - this file must be available as "config/filesystem.json"
+	// - some locations are hardcoded (user config directory, cache/tmp directory, saved games location)
 	"filesystem":
 	{
 		"DATA/" :
 		[
-			{"type" : "lod", "path" : "ALL/Data/H3ab_bmp.lod"},
-			{"type" : "lod", "path" : "ALL/Data/H3bitmap.lod"},
-			{"type" : "dir",  "path" : "ALL/Data"}
+			{"type" : "lod", "path" : "Data/H3ab_bmp.lod"},
+			{"type" : "lod", "path" : "Data/H3bitmap.lod"},
+			{"type" : "dir",  "path" : "Data"}
 		],
 		"SPRITES/":
 		[
-			{"type" : "lod", "path" : "ALL/Data/H3ab_spr.lod"},
-			{"type" : "lod", "path" : "ALL/Data/H3sprite.lod"},
-			{"type" : "dir",  "path" : "ALL/Sprites"}
+			{"type" : "lod", "path" : "Data/H3ab_spr.lod"},
+			{"type" : "lod", "path" : "Data/H3sprite.lod"},
+			{"type" : "dir",  "path" : "Sprites"}
 		],
 		"SOUNDS/":
 		[
-			{"type" : "snd", "path" : "ALL/Data/H3ab_ahd.snd"},
-			{"type" : "snd", "path" : "ALL/Data/Heroes3-cd2.snd"},
-			{"type" : "snd", "path" : "ALL/Data/Heroes3.snd"},
+			{"type" : "snd", "path" : "Data/H3ab_ahd.snd"},
+			{"type" : "snd", "path" : "Data/Heroes3-cd2.snd"},
+			{"type" : "snd", "path" : "Data/Heroes3.snd"},
 			//WoG have overriden sounds with .82m extension in Data
-			{"type" : "dir",  "path" : "ALL/Data", "depth": 0}
+			{"type" : "dir",  "path" : "Data", "depth": 0}
 		],
 		"MUSIC/":
 		[
-			{"type" : "dir",  "path" : "ALL/Mp3"}
+			{"type" : "dir",  "path" : "Mp3"}
 		],
 		"VIDEO/":
 		[
-			{"type" : "vid", "path" : "ALL/Data/H3ab_ahd.vid"},
-			{"type" : "vid", "path" : "ALL/Data/Heroes3.vid"},
-			{"type" : "vid", "path" : "ALL/Data/video.vid"}
+			{"type" : "vid", "path" : "Data/H3ab_ahd.vid"},
+			{"type" : "vid", "path" : "Data/Heroes3.vid"},
+			{"type" : "vid", "path" : "Data/video.vid"}
 		],
 		"CONFIG/":
 		[
-			{"type" : "dir",  "path" : "GLOBAL/Config",}, // separate to avoid overwriting global resources
-			{"type" : "dir",  "path" : "LOCAL/Config", "writeable": true}
+			{"type" : "dir",  "path" : "config",}
 		],
 		"MAPS/":
 		[
-			{"type" : "dir",  "path" : "ALL/Maps"}
+			{"type" : "dir",  "path" : "Maps"}
 		],
 		"MODS/":
 		[
-			{"type" : "dir",  "path" : "ALL/Mods", "depth": 1}
-		],
-		"SAVES/":
-		[
-			{"type" : "dir",  "path" : "LOCAL/Games", "writeable": true},
+			{"type" : "dir",  "path" : "Mods", "depth": 1}
 		]
 	}
 }

+ 1 - 1
lib/BattleState.cpp

@@ -10,7 +10,7 @@
 #include "CTownHandler.h"
 #include "NetPacks.h"
 #include "JsonNode.h"
-#include "filesystem/CResourceLoader.h"
+#include "filesystem/Filesystem.h"
 
 
 /*

+ 1 - 1
lib/CArtHandler.cpp

@@ -1,7 +1,7 @@
 #include "StdInc.h"
 #include "CArtHandler.h"
 
-#include "filesystem/CResourceLoader.h"
+#include "filesystem/Filesystem.h"
 #include "CGeneralTextHandler.h"
 #include "VCMI_Lib.h"
 #include "CModHandler.h"

+ 1 - 3
lib/CBonusTypeHandler.cpp

@@ -11,9 +11,7 @@
 #include "CBonusTypeHandler.h"
 
 #include "JsonNode.h"
-#include "filesystem/CResourceLoader.h"
-#include "filesystem/ISimpleResourceLoader.h"
-
+#include "filesystem/Filesystem.h"
 
 #include "VCMI_Lib.h"
 #include "CCreatureHandler.h"

+ 2 - 2
lib/CConfigHandler.cpp

@@ -1,7 +1,7 @@
 #include "StdInc.h"
 #include "CConfigHandler.h"
 
-#include "../lib/filesystem/CResourceLoader.h"
+#include "../lib/filesystem/Filesystem.h"
 #include "../lib/GameConstants.h"
 #include "../lib/VCMIDirs.h"
 
@@ -80,7 +80,7 @@ void SettingsStorage::invalidateNode(const std::vector<std::string> &changedPath
 	savedConf.Struct().erase("session");
 	JsonUtils::minimize(savedConf, "vcmi:settings");
 
-	std::ofstream file(CResourceHandler::get()->getResourceName(ResourceID("config/settings.json")), std::ofstream::trunc);
+	std::ofstream file(*CResourceHandler::get()->getResourceName(ResourceID("config/settings.json")), std::ofstream::trunc);
 	file << savedConf;
 }
 

+ 1 - 1
lib/CCreatureHandler.cpp

@@ -2,7 +2,7 @@
 #include "CCreatureHandler.h"
 
 #include "CGeneralTextHandler.h"
-#include "filesystem/CResourceLoader.h"
+#include "filesystem/Filesystem.h"
 #include "VCMI_Lib.h"
 #include "CGameState.h"
 #include "CTownHandler.h"

+ 3 - 3
lib/CDefObjInfoHandler.cpp

@@ -1,7 +1,7 @@
 #include "StdInc.h"
 #include "CDefObjInfoHandler.h"
 
-#include "filesystem/CResourceLoader.h"
+#include "filesystem/Filesystem.h"
 #include "../client/CGameInfo.h"
 #include "../lib/VCMI_Lib.h"
 #include "GameConstants.h"
@@ -35,7 +35,7 @@ CGDefInfo::CGDefInfo()
 void CGDefInfo::fetchInfoFromMSK()
 {
 
-	auto msk = CResourceHandler::get()->loadData(ResourceID(std::string("SPRITES/") + name, EResType::MASK));
+	auto msk = CResourceHandler::get()->load(ResourceID(std::string("SPRITES/") + name, EResType::MASK))->readAll();
 
 	width = msk.first.get()[0];
 	height = msk.first.get()[1];
@@ -50,7 +50,7 @@ CDefObjInfoHandler::CDefObjInfoHandler()
 {
 	VLC->dobjinfo = this;
 
-	auto textFile = CResourceHandler::get()->loadData(ResourceID("DATA/OBJECTS.TXT"));
+	auto textFile = CResourceHandler::get()->load(ResourceID("DATA/OBJECTS.TXT"))->readAll();
 
 	std::istringstream inp(std::string((char*)textFile.first.get(), textFile.second));
 	int objNumber;

+ 1 - 1
lib/CGameState.cpp

@@ -22,7 +22,7 @@
 #include "mapping/CMapInfo.h"
 #include "BattleState.h"
 #include "JsonNode.h"
-#include "filesystem/CResourceLoader.h"
+#include "filesystem/Filesystem.h"
 #include "GameConstants.h"
 #include "rmg/CMapGenerator.h"
 #include "CStopWatch.h"

+ 2 - 3
lib/CGeneralTextHandler.cpp

@@ -1,8 +1,7 @@
 #include "StdInc.h"
 #include "CGeneralTextHandler.h"
 
-#include "filesystem/CResourceLoader.h"
-#include "filesystem/CInputStream.h"
+#include "filesystem/Filesystem.h"
 #include "GameConstants.h"
 #include "CModHandler.h"
 #include "VCMI_Lib.h"
@@ -322,7 +321,7 @@ CGeneralTextHandler::CGeneralTextHandler()
 	}
 
 	std::string buffer;
-	std::ifstream ifs(CResourceHandler::get()->getResourceName(ResourceID("config/threatlevel.txt")), std::ios::binary);
+	std::ifstream ifs(*CResourceHandler::get()->getResourceName(ResourceID("config/threatlevel.txt")), std::ios::binary);
 	getline(ifs, buffer); //skip 1st line
 	for (int i = 0; i < 13; ++i)
 	{

+ 1 - 1
lib/CHeroHandler.cpp

@@ -2,7 +2,7 @@
 #include "CHeroHandler.h"
 
 #include "CGeneralTextHandler.h"
-#include "filesystem/CResourceLoader.h"
+#include "filesystem/Filesystem.h"
 #include "VCMI_Lib.h"
 #include "JsonNode.h"
 #include "StringConstants.h"

+ 10 - 6
lib/CMakeLists.txt

@@ -5,24 +5,28 @@ include_directories(${CMAKE_HOME_DIRECTORY} ${CMAKE_CURRENT_SOURCE_DIRECTORY} ${
 include_directories(${Boost_INCLUDE_DIRS} ${SDL_INCLUDE_DIR} ${ZLIB_INCLUDE_DIR})
 
 set(lib_SRCS
-		filesystem/CBinaryReader.cpp
+		filesystem/AdapterLoaders.cpp
+		filesystem/CCompressedStream.cpp
 		filesystem/CFilesystemLoader.cpp
-		filesystem/CMemoryStream.cpp
+		filesystem/CArchiveLoader.cpp
 		filesystem/CFileInfo.cpp
-		filesystem/CLodArchiveLoader.cpp
-		filesystem/CResourceLoader.cpp
+		filesystem/CMemoryStream.cpp
+		filesystem/CBinaryReader.cpp
 		filesystem/CFileInputStream.cpp
-		filesystem/CCompressedStream.cpp
-		filesystem/CMappedFileLoader.cpp
+		filesystem/Filesystem.cpp
+
 		logging/CBasicLogConfigurator.cpp
 		logging/CLogger.cpp
+
 		mapping/CCampaignHandler.cpp
 		mapping/CMap.cpp
 		mapping/CMapEditManager.cpp
 		mapping/CMapInfo.cpp
 		mapping/CMapService.cpp
 		mapping/MapFormatH3M.cpp
+
 		rmg/CMapGenerator.cpp
+
 		BattleAction.cpp
 		BattleHex.cpp
 		BattleState.cpp

+ 2 - 3
lib/CModHandler.cpp

@@ -2,8 +2,7 @@
 #include "CModHandler.h"
 #include "CDefObjInfoHandler.h"
 #include "JsonNode.h"
-#include "filesystem/CResourceLoader.h"
-#include "filesystem/ISimpleResourceLoader.h"
+#include "filesystem/Filesystem.h"
 
 #include "CCreatureHandler.h"
 #include "CArtHandler.h"
@@ -497,7 +496,7 @@ void CModHandler::initialize(std::vector<std::string> availableMods)
 	modConfig["activeMods"] = resultingList;
 	CResourceHandler::get()->createResource("CONFIG/modSettings.json");
 
-	std::ofstream file(CResourceHandler::get()->getResourceName(ResourceID("config/modSettings.json")), std::ofstream::trunc);
+	std::ofstream file(*CResourceHandler::get()->getResourceName(ResourceID("config/modSettings.json")), std::ofstream::trunc);
 	file << modConfig;
 }
 

+ 1 - 1
lib/CModHandler.h

@@ -1,6 +1,6 @@
 #pragma once
 
-#include "filesystem/CResourceLoader.h"
+#include "filesystem/Filesystem.h"
 
 #include "VCMI_Lib.h"
 #include "JsonNode.h"

+ 1 - 1
lib/CObjectHandler.cpp

@@ -29,7 +29,7 @@
 #include <SDL_stdinc.h>
 #include "CBuildingHandler.h"
 #include "JsonNode.h"
-#include "filesystem/CResourceLoader.h"
+#include "filesystem/Filesystem.h"
 
 using namespace boost::assign;
 

+ 1 - 1
lib/CSpellHandler.cpp

@@ -2,7 +2,7 @@
 #include "CSpellHandler.h"
 
 #include "CGeneralTextHandler.h"
-#include "filesystem/CResourceLoader.h"
+#include "filesystem/Filesystem.h"
 #include "VCMI_Lib.h"
 #include "JsonNode.h"
 #include <cctype>

+ 1 - 1
lib/CTownHandler.cpp

@@ -9,7 +9,7 @@
 #include "CHeroHandler.h"
 #include "CArtHandler.h"
 #include "CSpellHandler.h"
-#include "filesystem/CResourceLoader.h"
+#include "filesystem/Filesystem.h"
 
 /*
  * CTownHandler.cpp, part of VCMI engine

+ 5 - 9
lib/JsonNode.cpp

@@ -14,8 +14,7 @@
 #include "ScopeGuard.h"
 
 #include "HeroBonus.h"
-#include "filesystem/CResourceLoader.h"
-#include "filesystem/ISimpleResourceLoader.h"
+#include "filesystem/Filesystem.h"
 #include "VCMI_Lib.h" //for identifier resolution
 #include "CModHandler.h"
 
@@ -42,7 +41,7 @@ JsonNode::JsonNode(const char *data, size_t datasize):
 JsonNode::JsonNode(ResourceID && fileURI):
 	type(DATA_NULL)
 {
-	auto file = CResourceHandler::get()->loadData(fileURI);
+	auto file = CResourceHandler::get()->load(fileURI)->readAll();
 
 	JsonParser parser(reinterpret_cast<char*>(file.first.get()), file.second);
 	*this = parser.parse(fileURI.getName());
@@ -1610,19 +1609,16 @@ JsonNode JsonUtils::assembleFromFiles(std::vector<std::string> files)
 JsonNode JsonUtils::assembleFromFiles(std::string filename)
 {
 	JsonNode result;
+	ResourceID resID(filename, EResType::TEXT);
 
-	auto & configList = CResourceHandler::get()->getResourcesWithName(ResourceID(filename, EResType::TEXT));
-
-	for(auto & entry : configList)
+	for(auto & loader : CResourceHandler::get()->getResourcesWithName(resID))
 	{
 		// FIXME: some way to make this code more readable
-		auto stream = entry.getLoader()->load(entry.getResourceName());
+		auto stream = loader->load(resID);
 		std::unique_ptr<ui8[]> textData(new ui8[stream->getSize()]);
 		stream->read(textData.get(), stream->getSize());
 
 		JsonNode section((char*)textData.get(), stream->getSize());
-		//for debug
-		//section.setMeta(entry.getLoader()->getOrigin());
 		merge(result, section);
 	}
 	return result;

+ 6 - 6
lib/VCMI_Lib.cpp

@@ -25,7 +25,7 @@
 #include "IGameEventsReceiver.h"
 #include "CStopWatch.h"
 #include "VCMIDirs.h"
-#include "filesystem/CResourceLoader.h"
+#include "filesystem/Filesystem.h"
 #include "CConsoleHandler.h"
 
 LibClasses * VLC = nullptr;
@@ -34,20 +34,20 @@ DLL_LINKAGE void preinitDLL(CConsoleHandler *Console)
 {
 	console = Console;
 	VLC = new LibClasses;
-	try
+	//try
 	{
 		VLC->loadFilesystem();
 	}
-	HANDLE_EXCEPTION;
+	//HANDLE_EXCEPTION;
 }
 
 DLL_LINKAGE void loadDLLClasses()
 {
-	try
+	//try
 	{
 		VLC->init();
 	}
-	HANDLE_EXCEPTION;
+	//HANDLE_EXCEPTION;
 }
 
 const IBonusTypeHandler * LibClasses::getBth() const
@@ -63,7 +63,7 @@ void LibClasses::loadFilesystem()
 	CResourceHandler::initialize();
     logGlobal->infoStream()<<"\t Initialization: "<<loadTime.getDiff();
 
-	CResourceHandler::loadFileSystem("", "ALL/config/filesystem.json");
+	CResourceHandler::loadMainFileSystem("config/filesystem.json");
     logGlobal->infoStream()<<"\t Data loading: "<<loadTime.getDiff();
 
 	modh = new CModHandler;

+ 138 - 0
lib/filesystem/AdapterLoaders.cpp

@@ -0,0 +1,138 @@
+#include "StdInc.h"
+#include "AdapterLoaders.h"
+
+#include "../JsonNode.h"
+
+CMappedFileLoader::CMappedFileLoader(const std::string & mountPoint, const JsonNode &config)
+{
+	for(auto entry : config.Struct())
+	{
+		fileList[ResourceID(mountPoint + entry.first)] = ResourceID(mountPoint + entry.second.String());
+	}
+}
+
+std::unique_ptr<CInputStream> CMappedFileLoader::load(const ResourceID & resourceName) const
+{
+	return CResourceHandler::get()->load(fileList.at(resourceName));
+}
+
+bool CMappedFileLoader::existsResource(const ResourceID & resourceName) const
+{
+	return fileList.count(resourceName) != 0;
+}
+
+std::string CMappedFileLoader::getMountPoint() const
+{
+	return ""; // does not have any meaning with this type of data source
+}
+
+boost::optional<std::string> CMappedFileLoader::getResourceName(const ResourceID & resourceName) const
+{
+	return CResourceHandler::get()->getResourceName(fileList.at(resourceName));
+}
+
+std::unordered_set<ResourceID> CMappedFileLoader::getFilteredFiles(std::function<bool(const ResourceID &)> filter) const
+{
+	std::unordered_set<ResourceID> foundID;
+
+	for (auto & file : fileList)
+	{
+		if (filter(file.first))
+			foundID.insert(file.first);
+	}
+	return foundID;
+}
+
+std::unique_ptr<CInputStream> CFilesystemList::load(const ResourceID & resourceName) const
+{
+	// load resource from last loader that have it (last overriden version)
+	for (auto & loader : boost::adaptors::reverse(loaders))
+	{
+		if (loader->existsResource(resourceName))
+			return loader->load(resourceName);
+	}
+
+	throw std::runtime_error("Resource with name " + resourceName.getName() + " and type "
+		+ EResTypeHelper::getEResTypeAsString(resourceName.getType()) + " wasn't found.");
+}
+
+bool CFilesystemList::existsResource(const ResourceID & resourceName) const
+{
+	return !getResourcesWithName(resourceName).empty();
+}
+
+std::string CFilesystemList::getMountPoint() const
+{
+	return "";
+}
+
+boost::optional<std::string> CFilesystemList::getResourceName(const ResourceID & resourceName) const
+{
+	if (existsResource(resourceName))
+		return getResourcesWithName(resourceName).back()->getResourceName(resourceName);
+	return boost::optional<std::string>();
+}
+
+std::unordered_set<ResourceID> CFilesystemList::getFilteredFiles(std::function<bool(const ResourceID &)> filter) const
+{
+	std::unordered_set<ResourceID> ret;
+
+	for (auto & loader : loaders)
+		for (auto & entry : loader->getFilteredFiles(filter))
+			ret.insert(entry);
+
+	return ret;
+}
+
+bool CFilesystemList::createResource(std::string filename, bool update)
+{
+	logGlobal->traceStream()<< "Creating " << filename;
+	for (auto & loader : boost::adaptors::reverse(loaders))
+	{
+		if (writeableLoaders.count(loader.get()) != 0                       // writeable,
+			&& loader->createResource(filename, update))          // successfully created
+		{
+			// Check if resource was created successfully. Possible reasons for this to fail
+			// a) loader failed to create resource (e.g. read-only FS)
+			// b) in update mode, call with filename that does not exists
+			assert(load(ResourceID(filename)));
+
+			logGlobal->traceStream()<< "Resource created successfully";
+			return true;
+		}
+	}
+	logGlobal->traceStream()<< "Failed to create resource";
+	return false;
+}
+
+std::vector<const ISimpleResourceLoader *> CFilesystemList::getResourcesWithName(const ResourceID & resourceName) const
+{
+	std::vector<const ISimpleResourceLoader *> ret;
+
+	for (auto & loader : loaders)
+		boost::range::copy(loader->getResourcesWithName(resourceName), std::back_inserter(ret));
+
+	return ret;
+}
+
+void CFilesystemList::addLoader(ISimpleResourceLoader * loader, bool writeable)
+{
+	loaders.push_back(std::unique_ptr<ISimpleResourceLoader>(loader));
+	if (writeable)
+		writeableLoaders.insert(loader);
+}
+
+ISimpleResourceLoader * CResourceHandler::get()
+{
+	if(resourceLoader != nullptr)
+	{
+		return resourceLoader;
+	}
+	else
+	{
+		std::stringstream string;
+		string << "Error: Resource loader wasn't initialized. "
+			   << "Make sure that you set one via FilesystemFactory::initialize";
+		throw std::runtime_error(string.str());
+	}
+}

+ 79 - 0
lib/filesystem/AdapterLoaders.h

@@ -0,0 +1,79 @@
+#pragma once
+
+/*
+ * AdapterLoaders.h, 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 "ISimpleResourceLoader.h"
+#include "Filesystem.h"
+
+class CFileInfo;
+class CInputStream;
+
+/**
+ * Class that implements file mapping (aka *nix symbolic links)
+ * Uses json file as input, content is map:
+ * "fileA.txt" : "fileB.txt"
+ * Note that extension is necessary, but used only to determine type
+ *
+ * fileA - file which will be replaced
+ * fileB - file which will be used as replacement
+ */
+class DLL_LINKAGE CMappedFileLoader : public ISimpleResourceLoader
+{
+public:
+	/**
+	 * Ctor.
+	 *
+	 * @param config Specifies filesystem configuration
+	 */
+	explicit CMappedFileLoader(const std::string &mountPoint, const JsonNode & config);
+
+	/// Interface implementation
+	/// @see ISimpleResourceLoader
+	std::unique_ptr<CInputStream> load(const ResourceID & resourceName) const override;
+	bool existsResource(const ResourceID & resourceName) const override;
+	std::string getMountPoint() const override;
+	boost::optional<std::string> getResourceName(const ResourceID & resourceName) const override;
+	std::unordered_set<ResourceID> getFilteredFiles(std::function<bool(const ResourceID &)> filter) const;
+
+private:
+	/** A list of files in this map
+	 * key = ResourceID for resource loader
+	 * value = ResourceID to which file this request will be redirected
+	*/
+	std::unordered_map<ResourceID, ResourceID> fileList;
+};
+
+class DLL_LINKAGE CFilesystemList : public ISimpleResourceLoader
+{
+	std::vector<std::unique_ptr<ISimpleResourceLoader> > loaders;
+
+	std::set<ISimpleResourceLoader *> writeableLoaders;
+
+public:
+	/// Interface implementation
+	/// @see ISimpleResourceLoader
+	std::unique_ptr<CInputStream> load(const ResourceID & resourceName) const override;
+	bool existsResource(const ResourceID & resourceName) const override;
+	std::string getMountPoint() const override;
+	boost::optional<std::string> getResourceName(const ResourceID & resourceName) const override;
+	std::unordered_set<ResourceID> getFilteredFiles(std::function<bool(const ResourceID &)> filter) const override;
+	bool createResource(std::string filename, bool update = false) override;
+	std::vector<const ISimpleResourceLoader *> getResourcesWithName(const ResourceID & resourceName) const override;
+
+	/**
+	 * Adds a resource loader to the loaders list
+	 * Passes loader ownership to this object
+	 *
+	 * @param loader The simple resource loader object to add
+	 * @param writeable - resource shall be treated as writeable
+	 */
+	void addLoader(ISimpleResourceLoader * loader, bool writeable);
+};

+ 181 - 0
lib/filesystem/CArchiveLoader.cpp

@@ -0,0 +1,181 @@
+#include "StdInc.h"
+#include "CArchiveLoader.h"
+
+#include "CFileInputStream.h"
+#include "CCompressedStream.h"
+
+#include "CBinaryReader.h"
+#include "CFileInfo.h"
+
+ArchiveEntry::ArchiveEntry()
+	: offset(0), fullSize(0), compressedSize(0)
+{
+
+}
+
+CArchiveLoader::CArchiveLoader(const std::string &mountPoint, const std::string & archive):
+    archive(archive),
+    mountPoint(mountPoint)
+{
+	// Open archive file(.snd, .vid, .lod)
+	CFileInputStream fileStream(archive);
+
+	// Fake .lod file with no data has to be silently ignored.
+	if(fileStream.getSize() < 10)
+		return;
+
+	// Retrieve file extension of archive in uppercase
+	CFileInfo fileInfo(archive);
+	std::string ext = fileInfo.getExtension();
+	boost::to_upper(ext);
+
+	// Init the specific lod container format
+	if(ext == ".LOD" || ext == ".PAC")
+	{
+		initLODArchive(mountPoint, fileStream);
+	}
+	else if(ext == ".VID")
+	{
+		initVIDArchive(mountPoint, fileStream);
+	}
+	else if(ext == ".SND")
+	{
+		initSNDArchive(mountPoint, fileStream);
+	}
+	else
+	{
+		throw std::runtime_error("LOD archive format unknown. Cannot deal with " + archive);
+	}
+}
+
+void CArchiveLoader::initLODArchive(const std::string &mountPoint, CFileInputStream & fileStream)
+{
+	// Read count of total files
+	CBinaryReader reader(&fileStream);
+
+	fileStream.seek(8);
+	ui32 totalFiles = reader.readUInt32();
+
+	// Get all entries from file
+	fileStream.seek(0x5c);
+
+	// Insert entries to list
+	for(ui32 i = 0; i < totalFiles; i++)
+	{
+		char filename[16];
+		reader.read(reinterpret_cast<ui8*>(filename), 16);
+
+		// Create archive entry
+		ArchiveEntry entry;
+		entry.name     = filename;
+		entry.offset   = reader.readUInt32();
+		entry.fullSize = reader.readUInt32();
+		fileStream.skip(4); // unused, unknown
+		entry.compressedSize     = reader.readUInt32();
+
+		// Add lod entry to local entries map
+		entries[ResourceID(mountPoint + entry.name)] = entry;
+	}
+}
+
+void CArchiveLoader::initVIDArchive(const std::string &mountPoint, CFileInputStream & fileStream)
+{
+
+	// Read count of total files
+	CBinaryReader reader(&fileStream);
+	fileStream.seek(0);
+	ui32 totalFiles = reader.readUInt32();
+
+	std::set<int> offsets;
+
+	// Insert entries to list
+	for(ui32 i = 0; i < totalFiles; i++)
+	{
+		char filename[40];
+		reader.read(reinterpret_cast<ui8*>(filename), 40);
+
+		ArchiveEntry entry;
+		entry.name = filename;
+		entry.offset = reader.readInt32();
+		entry.compressedSize = 0;
+
+		offsets.insert(entry.offset);
+		entries[ResourceID(mountPoint + entry.name)] = entry;
+	}
+	offsets.insert(fileStream.getSize());
+
+	// now when we know postion of all files their sizes can be set correctly
+	for (auto & entry : entries)
+	{
+		auto it = offsets.find(entry.second.offset);
+		it++;
+		entry.second.fullSize = *it - entry.second.offset;
+	}
+}
+
+void CArchiveLoader::initSNDArchive(const std::string &mountPoint, CFileInputStream & fileStream)
+{
+	// Read count of total files
+	CBinaryReader reader(&fileStream);
+	fileStream.seek(0);
+	ui32 totalFiles = reader.readUInt32();
+
+	// Insert entries to list
+	for(ui32 i = 0; i < totalFiles; i++)
+	{
+		char filename[40];
+		reader.read(reinterpret_cast<ui8*>(filename), 40);
+
+		//for some reason entries in snd have format NAME\0WAVRUBBISH....
+		//we need to replace first \0 with dot and take the 3 chars with extension (and drop the rest)
+		ArchiveEntry entry;
+		entry.name  = filename; // till 1st \0
+		entry.name += '.';
+		entry.name += std::string(filename + entry.name.size(), 3);
+
+		entry.offset = reader.readInt32();
+		entry.fullSize = reader.readInt32();
+		entry.compressedSize = 0;
+		entries[ResourceID(mountPoint + entry.name)] = entry;
+	}
+}
+
+std::unique_ptr<CInputStream> CArchiveLoader::load(const ResourceID & resourceName) const
+{
+	assert(existsResource(resourceName));
+
+	const ArchiveEntry & entry = entries.at(resourceName);
+
+	if (entry.compressedSize != 0) //compressed data
+	{
+		std::unique_ptr<CInputStream> fileStream(new CFileInputStream(archive, entry.offset, entry.compressedSize));
+
+		return std::unique_ptr<CInputStream>(new CCompressedStream(std::move(fileStream), false, entry.fullSize));
+	}
+	else
+	{
+		return std::unique_ptr<CInputStream>(new CFileInputStream(archive, entry.offset, entry.fullSize));
+	}
+}
+
+bool CArchiveLoader::existsResource(const ResourceID & resourceName) const
+{
+	return entries.count(resourceName) != 0;
+}
+
+std::string CArchiveLoader::getMountPoint() const
+{
+	return mountPoint;
+}
+
+std::unordered_set<ResourceID> CArchiveLoader::getFilteredFiles(std::function<bool(const ResourceID &)> filter) const
+{
+	std::unordered_set<ResourceID> foundID;
+
+	for (auto & file : entries)
+	{
+		if (filter(file.first))
+			foundID.insert(file.first);
+	}
+	return foundID;
+}

+ 17 - 17
lib/filesystem/CLodArchiveLoader.h → lib/filesystem/CArchiveLoader.h

@@ -1,6 +1,7 @@
+#pragma once
 
 /*
- * CLodArchiveLoader.h, part of VCMI engine
+ * CArchiveLoader.h, part of VCMI engine
  *
  * Authors: listed in file AUTHORS in main folder
  *
@@ -9,9 +10,8 @@
  *
  */
 
-#pragma once
-
 #include "ISimpleResourceLoader.h"
+#include "Filesystem.h"
 
 class CFileInfo;
 class CFileInputStream;
@@ -33,16 +33,16 @@ struct ArchiveEntry
 	int offset;
 
 	/** Size without compression in bytes **/
-	int realSize;
+	int fullSize;
 
 	/** Size with compression in bytes or 0 if not compressed **/
-	int size;
+	int compressedSize;
 };
 
 /**
  * A class which can scan and load files of a LOD archive.
  */
-class DLL_LINKAGE CLodArchiveLoader : public ISimpleResourceLoader
+class DLL_LINKAGE CArchiveLoader : public ISimpleResourceLoader
 {
 public:
 	/**
@@ -55,42 +55,42 @@ public:
 	 *
 	 * @throws std::runtime_error if the archive wasn't found or if the archive isn't supported
 	 */
-	explicit CLodArchiveLoader(const std::string & archive);
+	explicit CArchiveLoader(const std::string & mountPoint, const std::string & archive);
 
 	/// Interface implementation
 	/// @see ISimpleResourceLoader
-	std::unique_ptr<CInputStream> load(const std::string & resourceName) const override;
-	std::unordered_map<ResourceID, std::string> getEntries() const override;
-	bool existsEntry(const std::string & resourceName) const override;
-	std::string getOrigin() const override;
+	std::unique_ptr<CInputStream> load(const ResourceID & resourceName) const override;
+	bool existsResource(const ResourceID & resourceName) const override;
+	std::string getMountPoint() const override;
+	std::unordered_set<ResourceID> getFilteredFiles(std::function<bool(const ResourceID &)> filter) const;
 
 private:
-	const ArchiveEntry * getArchiveEntry(const std::string & resourceName) const;
-
 	/**
 	 * Initializes a LOD archive.
 	 *
 	 * @param fileStream File stream to the .lod archive
 	 */
-	void initLODArchive(CFileInputStream & fileStream);
+	void initLODArchive(const std::string &mountPoint, CFileInputStream & fileStream);
 
 	/**
 	 * Initializes a VID archive.
 	 *
 	 * @param fileStream File stream to the .vid archive
 	 */
-	void initVIDArchive(CFileInputStream & fileStream);
+	void initVIDArchive(const std::string &mountPoint, CFileInputStream & fileStream);
 
 	/**
 	 * Initializes a SND archive.
 	 *
 	 * @param fileStream File stream to the .snd archive
 	 */
-	void initSNDArchive(CFileInputStream & fileStream);
+	void initSNDArchive(const std::string &mountPoint, CFileInputStream & fileStream);
 
 	/** The file path to the archive which is scanned and indexed. */
 	std::string archive;
 
+	std::string mountPoint;
+
 	/** Holds all entries of the archive file. An entry can be accessed via the entry name. **/
-	std::unordered_map<std::string, ArchiveEntry> entries;
+	std::unordered_map<ResourceID, ArchiveEntry> entries;
 };

+ 1 - 5
lib/filesystem/CBinaryReader.cpp

@@ -1,5 +1,6 @@
 #include "StdInc.h"
 #include "CBinaryReader.h"
+
 #include <SDL_endian.h>
 #include "CInputStream.h"
 
@@ -12,9 +13,7 @@ CData readLE(CData data)
 	std::reverse(dataPtr, dataPtr + sizeof(data));
 	return data;
 }
-
 #else
-
 template <typename CData>
 CData readLE(CData data)
 {
@@ -24,12 +23,10 @@ CData readLE(CData data)
 
 CBinaryReader::CBinaryReader() : stream(nullptr)
 {
-
 }
 
 CBinaryReader::CBinaryReader(CInputStream * stream) : stream(stream)
 {
-
 }
 
 CInputStream * CBinaryReader::getStream()
@@ -97,7 +94,6 @@ void CBinaryReader::skip(int count)
 	stream->skip(count);
 }
 
-
 std::string CBinaryReader::getEndOfStreamExceptionMsg(long bytesToRead) const
 {
 	std::stringstream ss;

+ 1 - 2
lib/filesystem/CBinaryReader.h

@@ -1,3 +1,4 @@
+#pragma once
 
 /*
  * CBinaryReader.h, part of VCMI engine
@@ -9,8 +10,6 @@
  *
  */
 
-#pragma once
-
 class CInputStream;
 
 /**

+ 0 - 2
lib/filesystem/CCompressedStream.cpp

@@ -81,8 +81,6 @@ si64 CCompressedStream::getSize()
 
 void CCompressedStream::decompressTill(si64 newSize)
 {
-	assert(newSize < 100 * 1024 * 1024); //just in case
-
 	if (inflateState == nullptr)
 		return; //file already decompressed
 

+ 1 - 2
lib/filesystem/CCompressedStream.h

@@ -1,3 +1,4 @@
+#pragma once
 
 /*
  * CLodStream.h, part of VCMI engine
@@ -9,8 +10,6 @@
  *
  */
 
-#pragma once
-
 #include "CInputStream.h"
 
 struct z_stream_s;

+ 2 - 3
lib/filesystem/CFileInfo.h

@@ -1,3 +1,4 @@
+#pragma once
 
 /*
  * CFileInfo.h, part of VCMI engine
@@ -9,9 +10,7 @@
  *
  */
 
-#pragma once
-
-#include "CResourceLoader.h"
+#include "Filesystem.h"
 
 /**
  * A class which holds information about a file.

+ 1 - 1
lib/filesystem/CFileInputStream.cpp

@@ -1,7 +1,7 @@
 #include "StdInc.h"
 #include "CFileInputStream.h"
-#include "CFileInfo.h"
 
+#include "CFileInfo.h"
 
 CFileInputStream::CFileInputStream(const std::string & file, si64 start, si64 size)
 {

+ 1 - 2
lib/filesystem/CFileInputStream.h

@@ -1,3 +1,4 @@
+#pragma once
 
 /*
  * CFileInputStream.h, part of VCMI engine
@@ -9,8 +10,6 @@
  *
  */
 
-#pragma once
-
 #include "CInputStream.h"
 
 class CFileInfo;

+ 46 - 25
lib/filesystem/CFilesystemLoader.cpp

@@ -4,55 +4,75 @@
 #include "CFileInfo.h"
 #include "CFileInputStream.h"
 
-CFilesystemLoader::CFilesystemLoader(const std::string & baseDirectory, size_t depth, bool initial):
+CFilesystemLoader::CFilesystemLoader(const std::string &mountPoint, const std::string & baseDirectory, size_t depth, bool initial):
     baseDirectory(baseDirectory),
-    fileList(listFiles(depth, initial))
+    mountPoint(mountPoint),
+    fileList(listFiles(mountPoint, depth, initial))
 {
 }
 
-std::unique_ptr<CInputStream> CFilesystemLoader::load(const std::string & resourceName) const
+std::unique_ptr<CInputStream> CFilesystemLoader::load(const ResourceID & resourceName) const
 {
-	std::unique_ptr<CInputStream> stream(new CFileInputStream(getOrigin() + '/' + resourceName));
+	assert(fileList.count(resourceName));
+
+	std::unique_ptr<CInputStream> stream(new CFileInputStream(baseDirectory + '/' + fileList.at(resourceName)));
 	return stream;
 }
 
-bool CFilesystemLoader::existsEntry(const std::string & resourceName) const
+bool CFilesystemLoader::existsResource(const ResourceID & resourceName) const
 {
-	for(auto & elem : fileList)
-	{
-		if(elem.second == resourceName)
-		{
-			return true;
-		}
-	}
+	return fileList.count(resourceName);
+}
 
-	return false;
+std::string CFilesystemLoader::getMountPoint() const
+{
+	return mountPoint;
 }
 
-std::unordered_map<ResourceID, std::string> CFilesystemLoader::getEntries() const
+boost::optional<std::string> CFilesystemLoader::getResourceName(const ResourceID & resourceName) const
 {
-	return fileList;
+	assert(existsResource(resourceName));
+
+	return baseDirectory + '/' + fileList.at(resourceName);
 }
 
-std::string CFilesystemLoader::getOrigin() const
+std::unordered_set<ResourceID> CFilesystemLoader::getFilteredFiles(std::function<bool(const ResourceID &)> filter) const
 {
-	return baseDirectory;
+	std::unordered_set<ResourceID> foundID;
+
+	for (auto & file : fileList)
+	{
+		if (filter(file.first))
+			foundID.insert(file.first);
+	}	return foundID;
 }
 
-bool CFilesystemLoader::createEntry(std::string filename, bool update)
+bool CFilesystemLoader::createResource(std::string filename, bool update)
 {
-	ResourceID res(filename);
-	if (fileList.find(res) != fileList.end())
+	ResourceID resID(filename);
+
+	if (fileList.find(resID) != fileList.end())
+		return true;
+
+	if (!boost::iequals(mountPoint, filename.substr(0, mountPoint.size())))
+	{
+		logGlobal->traceStream() << "Can't create file: wrong mount point: " << mountPoint;
 		return false;
+	}
+
+	filename = filename.substr(mountPoint.size());
 
-	fileList[res] = filename;
 	if (!update)
+	{
 		std::ofstream newfile (baseDirectory + "/" + filename);
+		if (!newfile.good())
+			return false;
+	}
+	fileList[resID] = filename;
 	return true;
 }
 
-
-std::unordered_map<ResourceID, std::string> CFilesystemLoader::listFiles(size_t depth, bool initial) const
+std::unordered_map<ResourceID, std::string> CFilesystemLoader::listFiles(const std::string &mountPoint, size_t depth, bool initial) const
 {
 	std::set<EResType::Type> initialTypes;
 	initialTypes.insert(EResType::DIRECTORY);
@@ -77,7 +97,8 @@ std::unordered_map<ResourceID, std::string> CFilesystemLoader::listFiles(size_t
 		{
 			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
+			// don't iterate into directory if depth limit reached OR into hidden directories (like .svn)
+			it.no_push(depth <= it.level() || it->path().leaf().string()[0] == '.');
 
 			type = EResType::DIRECTORY;
 		}
@@ -92,7 +113,7 @@ std::unordered_map<ResourceID, std::string> CFilesystemLoader::listFiles(size_t
 				filename += path[i] + '/';
 			filename += it->path().leaf().string();
 
-			fileList[ResourceID(filename, type)] = filename;
+			fileList[ResourceID(mountPoint + filename, type)] = filename;
 		}
 	}
 

+ 12 - 10
lib/filesystem/CFilesystemLoader.h

@@ -1,3 +1,4 @@
+#pragma once
 
 /*
  * CFilesystemLoader.h, part of VCMI engine
@@ -9,10 +10,8 @@
  *
  */
 
-#pragma once
-
 #include "ISimpleResourceLoader.h"
-#include "CResourceLoader.h"
+#include "Filesystem.h"
 
 class CFileInfo;
 class CInputStream;
@@ -31,20 +30,23 @@ public:
 	 *
 	 * @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, bool initial = false);
+	explicit CFilesystemLoader(const std::string & mountPoint, const std::string & baseDirectory, size_t depth = 16, bool initial = false);
 
 	/// Interface implementation
 	/// @see ISimpleResourceLoader
-	std::unique_ptr<CInputStream> load(const std::string & resourceName) const override;
-	bool existsEntry(const std::string & resourceName) const override;
-	std::unordered_map<ResourceID, std::string> getEntries() const override;
-	std::string getOrigin() const override;
-	bool createEntry(std::string filename, bool update) override;
+	std::unique_ptr<CInputStream> load(const ResourceID & resourceName) const override;
+	bool existsResource(const ResourceID & resourceName) const override;
+	std::string getMountPoint() const override;
+	bool createResource(std::string filename, bool update = false) override;
+	boost::optional<std::string> getResourceName(const ResourceID & resourceName) const override;
+	std::unordered_set<ResourceID> getFilteredFiles(std::function<bool(const ResourceID &)> filter) const;
 
 private:
 	/** The base directory which is scanned and indexed. */
 	std::string baseDirectory;
 
+	std::string mountPoint;
+
 	/** A list of files in the directory
 	 * key = ResourceID for resource loader
 	 * value = name that can be used to access file
@@ -60,5 +62,5 @@ private:
 	 * @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;
+	std::unordered_map<ResourceID, std::string> listFiles(const std::string &mountPoint, size_t depth, bool initial) const;
 };

+ 13 - 4
lib/filesystem/CInputStream.h

@@ -1,3 +1,4 @@
+#pragma once
 
 /*
  * CInputStream.h, part of VCMI engine
@@ -9,8 +10,6 @@
  *
  */
 
-#pragma once
-
 /**
  * Abstract class which provides method definitions for reading from a stream.
  */
@@ -62,7 +61,17 @@ public:
 	virtual si64 getSize() = 0;
 
 	/**
-	 * Closes the stream and releases any system resources associated with the stream explicitely.
+	 * @brief for convenience, reads whole stream at once
+	 *
+	 * @return pair, first = raw data, second = size of data
 	 */
-	//virtual void close() = 0;
+	std::pair<std::unique_ptr<ui8[]>, size_t> readAll()
+	{
+		std::unique_ptr<ui8[]> data(new ui8[getSize()]);
+
+		size_t readSize = read(data.get(), getSize());
+		assert(readSize == getSize());
+
+		return std::make_pair(std::move(data), getSize());
+	}
 };

+ 0 - 220
lib/filesystem/CLodArchiveLoader.cpp

@@ -1,220 +0,0 @@
-#include "StdInc.h"
-#include "CLodArchiveLoader.h"
-#include "CInputStream.h"
-#include "CFileInputStream.h"
-#include "CCompressedStream.h"
-#include "CBinaryReader.h"
-#include "CFileInfo.h"
-#include <SDL_endian.h>
-
-ArchiveEntry::ArchiveEntry()
-	: offset(0), realSize(0), size(0)
-{
-
-}
-
-CLodArchiveLoader::CLodArchiveLoader(const std::string & archive)
-{
-	// Open archive file(.snd, .vid, .lod)
-	this->archive = archive;
-	CFileInputStream fileStream(archive);
-
-	// Fake .lod file with no data has to be silently ignored.
-	if(fileStream.getSize() < 10)
-		return;
-
-	// Retrieve file extension of archive in uppercase
-	CFileInfo fileInfo(archive);
-	std::string ext = fileInfo.getExtension();
-	boost::to_upper(ext);
-
-	// Init the specific lod container format
-	if(ext == ".LOD" || ext == ".PAC")
-	{
-		initLODArchive(fileStream);
-	}
-	else if(ext == ".VID")
-	{
-		initVIDArchive(fileStream);
-	}
-	else if(ext == ".SND")
-	{
-		initSNDArchive(fileStream);
-	}
-	else
-	{
-		throw std::runtime_error("LOD archive format unknown. Cannot deal with " + archive);
-	}
-}
-
-void CLodArchiveLoader::initLODArchive(CFileInputStream & fileStream)
-{
-	// Read count of total files
-	CBinaryReader reader(&fileStream);
-
-	fileStream.seek(8);
-	ui32 totalFiles = reader.readUInt32();
-
-	// Get all entries from file
-	fileStream.seek(0x5c);
-
-	// Insert entries to list
-	for(ui32 i = 0; i < totalFiles; i++)
-	{
-		char filename[16];
-		reader.read(reinterpret_cast<ui8*>(filename), 16);
-
-		// Create archive entry
-		ArchiveEntry entry;
-		entry.name     = filename;
-		entry.offset   = reader.readUInt32();
-		entry.realSize = reader.readUInt32();
-		fileStream.skip(4); // unused, unknown
-		entry.size     = reader.readUInt32();
-
-		// Add lod entry to local entries map
-		entries[entry.name] = entry;
-	}
-}
-
-void CLodArchiveLoader::initVIDArchive(CFileInputStream & fileStream)
-{
-	// Define VideoEntryBlock struct
-	struct VideoEntryBlock
-	{
-		char filename[40];
-		ui32 offset;
-	};
-
-	// Read count of total files
-    CBinaryReader reader(&fileStream);
-	fileStream.seek(0);
-	ui32 totalFiles = reader.readUInt32();
-
-	// Get all entries from file
-	fileStream.seek(4);
-	auto  vidEntries = new struct VideoEntryBlock[totalFiles];
-	fileStream.read(reinterpret_cast<ui8 *>(vidEntries), sizeof(struct VideoEntryBlock) * totalFiles);
-
-	// Insert entries to list
-	for(ui32 i = 0; i < totalFiles; i++)
-	{
-		VideoEntryBlock vidEntry = vidEntries[i];
-		ArchiveEntry entry;
-
-		entry.name = vidEntry.filename;
-		entry.offset = SDL_SwapLE32(vidEntry.offset);
-		entry.size = 0;
-
-		// There is no size, so check where the next file is
-		if (i == totalFiles - 1)
-		{
-			entry.realSize = fileStream.getSize() - entry.offset;
-		}
-		else
-		{
-			VideoEntryBlock nextVidEntry = vidEntries[i + 1];
-			entry.realSize = SDL_SwapLE32(nextVidEntry.offset) - entry.offset;
-		}
-
-		entries[entry.name] = entry;
-	}
-
-	// Delete vid entries array
-	delete[] vidEntries;
-}
-
-void CLodArchiveLoader::initSNDArchive(CFileInputStream & fileStream)
-{
-	// Define SoundEntryBlock struct
-	struct SoundEntryBlock
-	{
-		char filename[40];
-		ui32 offset;
-		ui32 size;
-	};
-
-	// Read count of total files
-    CBinaryReader reader(&fileStream);
-	fileStream.seek(0);
-	ui32 totalFiles = reader.readUInt32();
-
-	// Get all entries from file
-	fileStream.seek(4);
-	auto  sndEntries = new struct SoundEntryBlock[totalFiles];
-	fileStream.read(reinterpret_cast<ui8 *>(sndEntries), sizeof(struct SoundEntryBlock) * totalFiles);
-
-	// Insert entries to list
-	for(ui32 i = 0; i < totalFiles; i++)
-	{
-		SoundEntryBlock sndEntry = sndEntries[i];
-		ArchiveEntry entry;
-
-		//for some reason entries in snd have format NAME\0WAVRUBBISH....
-		//we need to replace first \0 with dot and take the 3 chars with extension (and drop the rest)
-		entry.name = std::string(sndEntry.filename, 40);
-		entry.name.resize(entry.name.find_first_of('\0') + 4); //+4 because we take dot and 3-char extension
-		entry.name[entry.name.find_first_of('\0')] = '.';
-
-		entry.offset = SDL_SwapLE32(sndEntry.offset);
-		entry.realSize = SDL_SwapLE32(sndEntry.size);
-		entry.size = 0;
-		entries[entry.name] = entry;
-	}
-
-	// Delete snd entries array
-	delete[] sndEntries;
-}
-
-std::unique_ptr<CInputStream> CLodArchiveLoader::load(const std::string & resourceName) const
-{
-	assert(existsEntry(resourceName));
-
-	const ArchiveEntry & entry = entries.find(resourceName)->second;
-
-	if (entry.size != 0) //compressed data
-	{
-		std::unique_ptr<CInputStream> fileStream(new CFileInputStream(getOrigin(), entry.offset, entry.size));
-
-		return std::unique_ptr<CInputStream>(new CCompressedStream(std::move(fileStream), false, entry.realSize));
-	}
-	else
-	{
-		return std::unique_ptr<CInputStream>(new CFileInputStream(getOrigin(), entry.offset, entry.realSize));
-	}
-}
-
-std::unordered_map<ResourceID, std::string> CLodArchiveLoader::getEntries() const
-{
-	std::unordered_map<ResourceID, std::string> retList;
-
-	for(auto & elem : entries)
-	{
-		const ArchiveEntry & entry = elem.second;
-		retList[ResourceID(entry.name)] = entry.name;
-	}
-
-	return retList;
-}
-
-const ArchiveEntry * CLodArchiveLoader::getArchiveEntry(const std::string & resourceName) const
-{
-	auto it = entries.find(resourceName);
-
-	if(it != entries.end())
-	{
-		return &(it->second);
-	}
-
-	return nullptr;
-}
-
-bool CLodArchiveLoader::existsEntry(const std::string & resourceName) const
-{
-	return entries.find(resourceName) != entries.end();
-}
-
-std::string CLodArchiveLoader::getOrigin() const
-{
-	return archive;
-}

+ 0 - 45
lib/filesystem/CMappedFileLoader.cpp

@@ -1,45 +0,0 @@
-#include "StdInc.h"
-#include "CMappedFileLoader.h"
-#include "CResourceLoader.h"
-#include "../JsonNode.h"
-
-CMappedFileLoader::CMappedFileLoader(const JsonNode &config)
-{
-	for(auto entry : config.Struct())
-	{
-		fileList[ResourceID(entry.first)] = entry.second.String();
-	}
-}
-
-std::unique_ptr<CInputStream> CMappedFileLoader::load(const std::string & resourceName) const
-{
-	return CResourceHandler::get()->load(ResourceID(resourceName));
-}
-
-bool CMappedFileLoader::existsEntry(const std::string & resourceName) const
-{
-	for(auto & elem : fileList)
-	{
-		if(elem.second == resourceName)
-		{
-			return true;
-		}
-	}
-
-	return false;
-}
-
-std::unordered_map<ResourceID, std::string> CMappedFileLoader::getEntries() const
-{
-	return fileList;
-}
-
-std::string CMappedFileLoader::getOrigin() const
-{
-	return ""; // does not have any meaning with this type of data source
-}
-
-std::string CMappedFileLoader::getFullName(const std::string & resourceName) const
-{
-	return CResourceHandler::get()->getResourceName(ResourceID(resourceName));
-}

+ 0 - 53
lib/filesystem/CMappedFileLoader.h

@@ -1,53 +0,0 @@
-
-/*
- * CMappedFileLoader.h, 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
- *
- */
-
-#pragma once
-
-#include "ISimpleResourceLoader.h"
-#include "CResourceLoader.h"
-
-class CFileInfo;
-class CInputStream;
-
-/**
- * Class that implements file mapping (aka *nix symbolic links)
- * Uses json file as input, content is map:
- * "fileA.txt" : "fileB.txt"
- * Note that extension is necessary, but used only to determine type
- *
- * fileA - file which will be replaced
- * fileB - file which will be used as replacement
- */
-class DLL_LINKAGE CMappedFileLoader : public ISimpleResourceLoader
-{
-public:
-	/**
-	 * Ctor.
-	 *
-	 * @param config Specifies filesystem configuration
-	 */
-	explicit CMappedFileLoader(const JsonNode & config);
-
-	/// Interface implementation
-	/// @see ISimpleResourceLoader
-	std::unique_ptr<CInputStream> load(const std::string & resourceName) const override;
-	bool existsEntry(const std::string & resourceName) const override;
-	std::unordered_map<ResourceID, std::string> getEntries() const override;
-	std::string getOrigin() const override;
-	std::string getFullName(const std::string & resourceName) const override;
-
-private:
-	/** A list of files in this map
-	 * key = ResourceID for resource loader
-	 * value = ResourceID to which file this request will be redirected
-	*/
-	std::unordered_map<ResourceID, std::string> fileList;
-};

+ 1 - 2
lib/filesystem/CMemoryStream.h

@@ -1,3 +1,4 @@
+#pragma once
 
 /*
  * CMemoryStream.h, part of VCMI engine
@@ -9,8 +10,6 @@
  *
  */
 
-#pragma once
-
 #include "CInputStream.h"
 
 /**

+ 0 - 491
lib/filesystem/CResourceLoader.cpp

@@ -1,491 +0,0 @@
-#include "StdInc.h"
-#include "CResourceLoader.h"
-#include "CFileInfo.h"
-#include "CLodArchiveLoader.h"
-#include "CFilesystemLoader.h"
-#include "CMappedFileLoader.h"
-
-//For filesystem initialization
-#include "../JsonNode.h"
-#include "../GameConstants.h"
-#include "../VCMIDirs.h"
-#include "../CStopWatch.h"
-
-CResourceLoader * CResourceHandler::resourceLoader = nullptr;
-CResourceLoader * CResourceHandler::initialLoader = nullptr;
-
-ResourceID::ResourceID()
-    :type(EResType::OTHER)
-{
-}
-
-ResourceID::ResourceID(std::string name)
-{
-	CFileInfo info(std::move(name));
-	setName(info.getStem());
-	setType(info.getType());
-}
-
-ResourceID::ResourceID(std::string name, EResType::Type type)
-{
-	setName(std::move(name));
-	setType(type);
-}
-
-ResourceID::ResourceID(const std::string & prefix, const std::string & name, EResType::Type type)
-{
-	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);
-}
-
-std::string ResourceID::getName() const
-{
-	return name;
-}
-
-EResType::Type ResourceID::getType() const
-{
-	return type;
-}
-
-void ResourceID::setName(std::string name)
-{
-	this->name = std::move(name);
-
-	size_t dotPos = this->name.find_last_of("/.");
-
-	if(dotPos != std::string::npos && this->name[dotPos] == '.')
-		this->name.erase(dotPos);
-
-	// strangely enough but this line takes 40-50% of filesystem loading time
-	boost::to_upper(this->name);
-}
-
-void ResourceID::setType(EResType::Type type)
-{
-	this->type = type;
-}
-
-CResourceLoader::CResourceLoader()
-{
-}
-
-std::unique_ptr<CInputStream> CResourceLoader::load(const ResourceID & resourceIdent) const
-{
-	auto resource = resources.find(resourceIdent);
-
-	if(resource == resources.end())
-	{
-		throw std::runtime_error("Resource with name " + resourceIdent.getName() + " and type "
-			+ EResTypeHelper::getEResTypeAsString(resourceIdent.getType()) + " wasn't found.");
-	}
-
-	// get the last added resource(most overriden)
-	const ResourceLocator & locator = resource->second.back();
-
-	// load the resource and return it
-	return locator.getLoader()->load(locator.getResourceName());
-}
-
-std::pair<std::unique_ptr<ui8[]>, ui64> CResourceLoader::loadData(const ResourceID & resourceIdent) const
-{
-	auto stream = load(resourceIdent);
-	std::unique_ptr<ui8[]> data(new ui8[stream->getSize()]);
-	size_t readSize = stream->read(data.get(), stream->getSize());
-
-	assert(readSize == stream->getSize());
-	return std::make_pair(std::move(data), stream->getSize());
-}
-
-ResourceLocator CResourceLoader::getResource(const ResourceID & resourceIdent) const
-{
-	auto resource = resources.find(resourceIdent);
-
-	if (resource == resources.end())
-		return ResourceLocator(nullptr, "");
-	return resource->second.back();
-}
-
-const std::vector<ResourceLocator> & CResourceLoader::getResourcesWithName(const ResourceID & resourceIdent) const
-{
-	static const std::vector<ResourceLocator> emptyList;
-	auto resource = resources.find(resourceIdent);
-
-	if (resource == resources.end())
-		return emptyList;
-	return resource->second;
-}
-
-
-std::string CResourceLoader::getResourceName(const ResourceID & resourceIdent) const
-{
-	auto locator = getResource(resourceIdent);
-	if (locator.getLoader())
-		return locator.getLoader()->getFullName(locator.getResourceName());
-	return "";
-}
-
-bool CResourceLoader::existsResource(const ResourceID & resourceIdent) const
-{
-	return resources.find(resourceIdent) != resources.end();
-}
-
-bool CResourceLoader::createResource(std::string URI, bool update)
-{
-	std::string filename = URI;
-	boost::to_upper(URI);
-	for (auto & entry : boost::adaptors::reverse(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))
-				continue;
-
-			resources[ResourceID(URI)].push_back(ResourceLocator(entry.loader.get(), filename));
-
-			// Check if resource was created successfully. Possible reasons for this to fail
-			// a) loader failed to create resource (e.g. read-only FS)
-			// b) in update mode, call with filename that does not exists
-			assert(load(ResourceID(URI)));
-
-			return true;
-		}
-	}
-	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
-	const std::unordered_map<ResourceID, std::string> & entries = loader->getEntries();
-
-	boost::to_upper(mountPoint);
-
-	for (auto & entry : entries)
-	{
-		// Create identifier and locator and add them to the resources list
-		ResourceID ident(mountPoint, entry.first.getName(), entry.first.getType());
-		ResourceLocator locator(loader.get(), entry.second);
-
-		resources[ident].push_back(locator);
-	}
-}
-
-CResourceLoader * CResourceHandler::get()
-{
-	if(resourceLoader != nullptr)
-	{
-		return resourceLoader;
-	}
-	else
-	{
-		std::stringstream string;
-		string << "Error: Resource loader wasn't initialized. "
-			   << "Make sure that you set one via CResourceLoaderFactory::initialize";
-		throw std::runtime_error(string.str());
-	}
-}
-
-void CResourceHandler::clear()
-{
-	delete resourceLoader;
-	delete initialLoader;
-}
-
-//void CResourceLoaderFactory::setInstance(CResourceLoader * resourceLoader)
-//{
-//	CResourceLoaderFactory::resourceLoader = resourceLoader;
-//}
-
-ResourceLocator::ResourceLocator(ISimpleResourceLoader * loader, const std::string & resourceName)
-			: loader(loader), resourceName(resourceName)
-{
-
-}
-
-ISimpleResourceLoader * ResourceLocator::getLoader() const
-{
-	return loader;
-}
-
-std::string ResourceLocator::getResourceName() const
-{
-	return resourceName;
-}
-
-EResType::Type EResTypeHelper::getTypeFromExtension(std::string extension)
-{
-	boost::to_upper(extension);
-
-	static const std::map<std::string, EResType::Type> stringToRes =
-	        boost::assign::map_list_of
-	        (".TXT",   EResType::TEXT)
-	        (".JSON",  EResType::TEXT)
-	        (".DEF",   EResType::ANIMATION)
-	        (".MSK",   EResType::MASK)
-	        (".MSG",   EResType::MASK)
-	        (".H3C",   EResType::CAMPAIGN)
-	        (".H3M",   EResType::MAP)
-	        (".FNT",   EResType::BMP_FONT)
-	        (".TTF",   EResType::TTF_FONT)
-	        (".BMP",   EResType::IMAGE)
-	        (".JPG",   EResType::IMAGE)
-	        (".PCX",   EResType::IMAGE)
-	        (".PNG",   EResType::IMAGE)
-	        (".TGA",   EResType::IMAGE)
-	        (".WAV",   EResType::SOUND)
-	        (".82M",   EResType::SOUND)
-	        (".SMK",   EResType::VIDEO)
-	        (".BIK",   EResType::VIDEO)
-	        (".MJPG",  EResType::VIDEO)
-	        (".MPG",   EResType::VIDEO)
-	        (".AVI",   EResType::VIDEO)
-	        (".MP3",   EResType::MUSIC)
-	        (".OGG",   EResType::MUSIC)
-	        (".LOD",   EResType::ARCHIVE_LOD)
-	        (".PAC",   EResType::ARCHIVE_LOD)
-	        (".VID",   EResType::ARCHIVE_VID)
-	        (".SND",   EResType::ARCHIVE_SND)
-	        (".PAL",   EResType::PALETTE)
-	        (".VCGM1", EResType::CLIENT_SAVEGAME)
-	        (".VSGM1", EResType::SERVER_SAVEGAME)
-	        (".ERM",   EResType::ERM)
-	        (".ERT",   EResType::ERT)
-	        (".ERS",   EResType::ERS);
-
-	auto iter = stringToRes.find(extension);
-	if (iter == stringToRes.end())
-		return EResType::OTHER;
-	return iter->second;
-}
-
-std::string EResTypeHelper::getEResTypeAsString(EResType::Type type)
-{
-#define MAP_ENUM(value) (EResType::value, #value)
-
-	static const std::map<EResType::Type, std::string> stringToRes = boost::assign::map_list_of
-		MAP_ENUM(TEXT)
-		MAP_ENUM(ANIMATION)
-		MAP_ENUM(MASK)
-		MAP_ENUM(CAMPAIGN)
-		MAP_ENUM(MAP)
-		MAP_ENUM(BMP_FONT)
-		MAP_ENUM(TTF_FONT)
-		MAP_ENUM(IMAGE)
-		MAP_ENUM(VIDEO)
-		MAP_ENUM(SOUND)
-		MAP_ENUM(MUSIC)
-		MAP_ENUM(ARCHIVE_LOD)
-		MAP_ENUM(ARCHIVE_SND)
-		MAP_ENUM(ARCHIVE_VID)
-		MAP_ENUM(PALETTE)
-		MAP_ENUM(CLIENT_SAVEGAME)
-		MAP_ENUM(SERVER_SAVEGAME)
-		MAP_ENUM(DIRECTORY)
-		MAP_ENUM(ERM)
-		MAP_ENUM(ERT)
-		MAP_ENUM(ERS)
-		MAP_ENUM(OTHER);
-
-#undef MAP_ENUM
-
-	auto iter = stringToRes.find(type);
-	assert(iter != stringToRes.end());
-
-	return iter->second;
-}
-
-void CResourceHandler::initialize()
-{
-	//recurse only into specific directories
-	auto recurseInDir = [](std::string URI, int depth)
-	{
-		auto resources = initialLoader->getResourcesWithName(ResourceID(URI, EResType::DIRECTORY));
-		for(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.
-	//used to solve several case-sensivity issues like Mp3 vs MP3
-	initialLoader = new CResourceLoader;
-	resourceLoader = new CResourceLoader;
-
-	for (auto path : VCMIDirs::get().dataPaths())
-	{
-		shared_ptr<ISimpleResourceLoader> loader(new CFilesystemLoader(path, 0, true));
-
-		initialLoader->addLoader("GLOBAL/", loader, false);
-		initialLoader->addLoader("ALL/", loader, false);
-	}
-
-	{
-		shared_ptr<ISimpleResourceLoader> loader(new CFilesystemLoader(VCMIDirs::get().userDataPath(), 0, true));
-
-		initialLoader->addLoader("LOCAL/", loader, false);
-
-		if (!vstd::contains(VCMIDirs::get().dataPaths(), VCMIDirs::get().userDataPath()))
-			initialLoader->addLoader("ALL/", loader, 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 spped issues if no mods present
-}
-
-void CResourceHandler::loadDirectory(const std::string &prefix, const std::string &mountPoint, const JsonNode & config)
-{
-	std::string URI = prefix + config["path"].String();
-	bool writeable = config["writeable"].Bool();
-	int depth = 16;
-	if (!config["depth"].isNull())
-		depth = config["depth"].Float();
-
-	auto resources = initialLoader->getResourcesWithName(ResourceID(URI, EResType::DIRECTORY));
-
-	for(const ResourceLocator & entry : resources)
-	{
-		std::string filename = entry.getLoader()->getOrigin() + '/' + entry.getResourceName();
-		resourceLoader->addLoader(mountPoint,
-		    shared_ptr<ISimpleResourceLoader>(new CFilesystemLoader(filename, depth)), writeable);
-	}
-}
-
-void CResourceHandler::loadArchive(const std::string &prefix, const std::string &mountPoint, const JsonNode & config, EResType::Type archiveType)
-{
-	std::string URI = prefix + config["path"].String();
-	std::string filename = initialLoader->getResourceName(ResourceID(URI, archiveType));
-	if (!filename.empty())
-		resourceLoader->addLoader(mountPoint,
-		    shared_ptr<ISimpleResourceLoader>(new CLodArchiveLoader(filename)), false);
-}
-
-void CResourceHandler::loadJsonMap(const std::string &prefix, const std::string &mountPoint, const JsonNode & config)
-{
-	std::string URI = prefix + config["path"].String();
-	std::string filename = initialLoader->getResourceName(ResourceID(URI, EResType::TEXT));
-	if (!filename.empty())
-	{
-		auto configData = initialLoader->loadData(ResourceID(URI, EResType::TEXT));
-
-		const JsonNode config((char*)configData.first.get(), configData.second);
-
-		resourceLoader->addLoader(mountPoint,
-		    shared_ptr<ISimpleResourceLoader>(new CMappedFileLoader(config)), false);
-	}
-}
-
-
-void CResourceHandler::loadFileSystem(const std::string & prefix, const std::string &fsConfigURI)
-{
-	auto fsConfigData = initialLoader->loadData(ResourceID(fsConfigURI, EResType::TEXT));
-
-	const JsonNode fsConfig((char*)fsConfigData.first.get(), fsConfigData.second);
-
-	loadFileSystem(prefix, fsConfig["filesystem"]);
-}
-
-void CResourceHandler::loadFileSystem(const std::string & prefix, const JsonNode &fsConfig)
-{
-	for(auto & mountPoint : fsConfig.Struct())
-	{
-		for(auto & entry : mountPoint.second.Vector())
-		{
-			CStopWatch timer;
-            logGlobal->debugStream() << "\t\tLoading resource at " << prefix + entry["path"].String();
-
-			if (entry["type"].String() == "map")
-				loadJsonMap(prefix, mountPoint.first, entry);
-			if (entry["type"].String() == "dir")
-				loadDirectory(prefix, mountPoint.first, entry);
-			if (entry["type"].String() == "lod")
-				loadArchive(prefix, mountPoint.first, entry, EResType::ARCHIVE_LOD);
-			if (entry["type"].String() == "snd")
-				loadArchive(prefix, mountPoint.first, entry, EResType::ARCHIVE_SND);
-			if (entry["type"].String() == "vid")
-				loadArchive(prefix, mountPoint.first, entry, EResType::ARCHIVE_VID);
-
-            logGlobal->debugStream() << "Resource loaded in " << timer.getDiff() << " ms.";
-		}
-	}
-}
-
-std::vector<std::string> CResourceHandler::getAvailableMods()
-{
-	auto iterator = initialLoader->getIterator([](const ResourceID & ident) ->  bool
-	{
-		std::string name = ident.getName();
-
-		return ident.getType() == EResType::DIRECTORY
-		    && std::count(name.begin(), name.end(), '/') == 2
-		    && boost::algorithm::starts_with(name, "ALL/MODS/");
-	});
-
-	//storage for found mods
-	std::vector<std::string> foundMods;
-	while (iterator.hasNext())
-	{
-		std::string name = iterator->getName();
-
-		name.erase(0, name.find_last_of('/') + 1);        //Remove path prefix
-
-		if (name == "WOG") // check if wog is actually present. Hack-ish but better than crash
-		{
-			if (!initialLoader->existsResource(ResourceID("ALL/DATA/ZVS", EResType::DIRECTORY)) &&
-			    !initialLoader->existsResource(ResourceID("ALL/MODS/WOG/DATA/ZVS", EResType::DIRECTORY)))
-			{
-				++iterator;
-				continue;
-			}
-		}
-
-		if (!name.empty()) // this is also triggered for "ALL/MODS/" entry
-			foundMods.push_back(name);
-
-		++iterator;
-	}
-	return foundMods;
-}
-
-void CResourceHandler::setActiveMods(std::vector<std::string> enabledMods)
-{
-	// default FS config for mods: directory "Content" that acts as H3 root directory
-	JsonNode defaultFS;
-
-	defaultFS[""].Vector().resize(1);
-	defaultFS[""].Vector()[0]["type"].String() = "dir";
-	defaultFS[""].Vector()[0]["path"].String() = "/Content";
-
-	for(std::string & modName : enabledMods)
-	{
-		ResourceID modConfFile("all/mods/" + modName + "/mod", EResType::TEXT);
-		auto fsConfigData = initialLoader->loadData(modConfFile);
-		const JsonNode fsConfig((char*)fsConfigData.first.get(), fsConfigData.second);
-
-		if (!fsConfig["filesystem"].isNull())
-			loadFileSystem("all/mods/" + modName, fsConfig["filesystem"]);
-		else
-			loadFileSystem("all/mods/" + modName, defaultFS);
-	}
-}

+ 0 - 446
lib/filesystem/CResourceLoader.h

@@ -1,446 +0,0 @@
-
-/*
- * CResourceLoader.h, 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
- *
- */
-
-#pragma once
-
-#include "CInputStream.h"
-
-class CResourceLoader;
-class ResourceLocator;
-class ISimpleResourceLoader;
-class JsonNode;
-
-/**
- * Specifies the resource type.
- *
- * Supported file extensions:
- *
- * Text: .txt .json
- * Animation: .def
- * Mask: .msk .msg
- * Campaign: .h3c
- * Map: .h3m
- * Font: .fnt
- * Image: .bmp, .jpg, .pcx, .png, .tga
- * Sound: .wav .82m
- * Video: .smk, .bik .mjpg .mpg
- * Music: .mp3, .ogg
- * Archive: .lod, .snd, .vid .pac
- * Palette: .pal
- * Savegame: .v*gm1
- */
-namespace EResType
-{
-	enum Type
-	{
-		TEXT,
-		ANIMATION,
-		MASK,
-		CAMPAIGN,
-		MAP,
-		BMP_FONT,
-		TTF_FONT,
-		IMAGE,
-		VIDEO,
-		SOUND,
-		MUSIC,
-		ARCHIVE_VID,
-		ARCHIVE_SND,
-		ARCHIVE_LOD,
-		PALETTE,
-		CLIENT_SAVEGAME,
-		SERVER_SAVEGAME,
-		DIRECTORY,
-		ERM,
-		ERT,
-		ERS,
-		OTHER
-	};
-}
-
-/**
- * A struct which identifies a resource clearly.
- */
-class DLL_LINKAGE ResourceID
-{
-public:
-	/**
-	 * Default c-tor.
-	 */
-	ResourceID();
-
-	/**
-	 * Move Ctor.
-	 */
-	ResourceID(ResourceID && other)
-		: name(std::move(other.name)), type(other.getType())
-	{
-
-	}
-
-	/**
-	 * Copy Ctor. Required by clang (or this is standard?) if move constructor is present
-	 */
-	ResourceID(const ResourceID & other)
-	    : name(other.getName()), type(other.getType())
-	{
-	}
-
-	/**
-	 * Ctor. Can be used to create indentifier for resource loading using one parameter
-	 *
-	 * @param name The resource name including extension.
-	 */
-	explicit ResourceID(std::string fullName);
-
-	/**
-	 * Ctor.
-	 *
-	 * @param name The resource name.
-	 * @param type The resource type. A constant from the enumeration EResType.
-	 */
-	ResourceID(std::string name, EResType::Type type);
-
-	/**
-	 * Compares this object with a another resource identifier.
-	 *
-	 * @param other The other resource identifier.
-	 * @return Returns true if both are equally, false if not.
-	 */
-	inline bool operator==(ResourceID const & other) const
-	{
-		return name == other.name && type == other.type;
-	}
-
-	/*
-	 * Move-assignment operator.
-	 */
-	inline ResourceID& operator=(ResourceID && other)
-	{
-		name = std::move(other.name);
-		type = other.getType();
-		return *this;
-	}
-
-	std::string getName() const;
-	EResType::Type getType() const;
-	void setName(std::string name);
-	void setType(EResType::Type type);
-
-protected:
-	/**
-	 * Ctor for usage strictly in resourceLoader for some speedup
-	 *
-	 * @param prefix Prefix of ths filename, already in upper case
-	 * @param name The resource name, upper case
-	 * @param type The resource type. A constant from the enumeration EResType.
-	 */
-	ResourceID(const std::string & prefix, const std::string & name, EResType::Type type);
-
-	friend class CResourceLoader;
-private:
-	/** Specifies the resource name. No extension so .pcx and .png can override each other, always in upper case. **/
-	std::string name;
-
-	/**
-	 * Specifies the resource type. EResType::OTHER if not initialized.
-	 * Required to prevent conflicts if files with different types (e.g. text and image) have the same name.
-	 */
-	EResType::Type type;
-};
-
-namespace std
-{
-	template <> struct hash<ResourceID>
-	{
-		size_t operator()(const ResourceID & resourceIdent) const
-		{
-			std::hash<int> intHasher;
-			std::hash<std::string> stringHasher;
-			return stringHasher(resourceIdent.getName()) ^ intHasher(static_cast<int>(resourceIdent.getType()));
-		}
-	};
-}
-
-/**
- * This class manages the loading of resources whether standard
- * or derived from several container formats and the file system.
- */
-class DLL_LINKAGE CResourceLoader
-{
-	typedef std::unordered_map<ResourceID, std::vector<ResourceLocator> > ResourcesMap;
-
-public:
-	/// class for iterating over all available files/Identifiers
-	/// can be created via CResourceLoader::getIterator
-	template <typename Comparator, typename Iter>
-	class Iterator
-	{
-	public:
-		/// find next available item.
-		Iterator& operator++()
-		{
-			assert(begin != end);
-			begin++;
-			findNext();
-			return *this;
-		}
-		bool hasNext()
-		{
-			return begin != end;
-		}
-
-		/// get identifier of current item
-		const ResourceID & operator* () const
-		{
-			assert(begin != end);
-			return begin->first;
-		}
-
-		/// get identifier of current item
-		const ResourceID * operator -> () const
-		{
-			assert(begin != end);
-			return &begin->first;
-		}
-
-	protected:
-		Iterator(Iter begin, Iter end, Comparator comparator):
-		    begin(begin),
-		    end(end),
-		    comparator(comparator)
-		{
-			//find first applicable item
-			findNext();
-		}
-
-		friend class CResourceLoader;
-
-	private:
-		Iter begin;
-		Iter end;
-		Comparator comparator;
-
-		void findNext()
-		{
-			while (begin != end && !comparator(begin->first))
-				begin++;
-		}
-
-	};
-
-	CResourceLoader();
-
-	/**
-	 * Loads the resource specified by the resource identifier.
-	 *
-	 * @param resourceIdent This parameter identifies the resource to load.
-	 * @return a pointer to the input stream, not null
-	 *
-	 * @throws std::runtime_error if the resource doesn't exists
-	 */
-	std::unique_ptr<CInputStream> load(const ResourceID & resourceIdent) const;
-	/// temporary member to ease transition to new filesystem classes
-	std::pair<std::unique_ptr<ui8[]>, ui64> loadData(const ResourceID & resourceIdent) const;
-
-	/**
-	 * Get resource locator for this identifier
-	 *
-	 * @param resourceIdent This parameter identifies the resource to load.
-	 * @return resource locator for this resource or empty one if resource was not found
-	 */
-	ResourceLocator getResource(const ResourceID & resourceIdent) const;
-
-	/// returns ALL overriden resources with same name, including last one acessible via getResource
-	const std::vector<ResourceLocator> & getResourcesWithName(const ResourceID & resourceIdent) const;
-
-	/// returns real name of file in filesystem. Not usable for archives
-	std::string getResourceName(const ResourceID & resourceIdent) const;
-
-	/**
-	 * Get iterator for looping all files matching filter
-	 * Notes:
-	 * - iterating over all files may be slow. Use with caution
-	 * - all filenames are in upper case
-	 *
-	 * @param filter functor with signature bool(ResourceIdentifier) used to check if this file is required
-	 * @return resource locator for this resource or empty one if resource was not found
-	 */
-	template<typename Comparator>
-	Iterator<Comparator, ResourcesMap::const_iterator> getIterator(Comparator filter) const
-	{
-		return Iterator<Comparator, ResourcesMap::const_iterator>(resources.begin(), resources.end(), filter);
-	}
-
-	/**
-	 * Tests whether the specified resource exists.
-	 *
-	 * @param resourceIdent the resource which should be checked
-	 * @return true if the resource exists, false if not
-	 */
-	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
-	 * @param update if true - this file is already present but not yet known by res handler
-	 * @return true on success, false if resource exists or on error
-	 */
-	bool createResource(std::string URI, bool update = false);
-
-	/**
-	 * Adds a simple resource loader to the loaders list and its entries to the resources list.
-	 *
-	 * The loader object will be destructed when this resource loader is destructed.
-	 * Don't delete it manually.
-	 * Same loader can be added multiple times (with different mount point)
-	 *
-	 * @param mountPoint prefix that will be added to all files in this loader
-	 * @param loader The simple resource loader object to add
-	 */
-	void addLoader(std::string mountPoint, shared_ptr<ISimpleResourceLoader> loader, bool writeable);
-
-private:
-	/**
-	 * Contains lists of same resources which can be accessed uniquely by an
-	 * resource identifier.
-	 */
-	ResourcesMap resources;
-
-	struct LoaderEntry
-	{
-		std::string prefix;
-		shared_ptr<ISimpleResourceLoader> loader;
-		bool writeable;
-	};
-
-	/** A list of resource loader objects */
-	std::vector<LoaderEntry > loaders;
-};
-
-/**
- * This class has static methods for a global resource loader access.
- *
- * Class is not thread-safe. Make sure nobody is calling getInstance while somebody else is calling initialize.
- */
-class DLL_LINKAGE CResourceHandler
-{
-public:
-	/**
-	 * Gets an instance of resource loader.
-	 *
-	 * Make sure that you've set an instance before using it. It'll throw an exception if no instance was set.
-	 *
-	 * @return Returns an instance of resource loader.
-	 */
-	static CResourceLoader * get();
-
-	/**
-	 * Creates instance of resource loader.
-	 * Will not fill filesystem with data
-	 *
-	 */
-	static void initialize();
-
-	/**
-	 * Semi-debug method to track all possible cases of memory leaks
-	 * Used before exiting application
-	 *
-	 */
-	static void clear();
-
-	/**
-	 * Will load all filesystem data from Json data at this path (config/filesystem.json)
-	 * @param prefix - prefix for all paths in filesystem config
-	 */
-	static void loadFileSystem(const std::string &prefix, const std::string & fsConfigURI);
-	static void loadFileSystem(const std::string &prefix, const JsonNode & fsConfig);
-	static void loadDirectory(const std::string &prefix, const std::string & mountPoint, const JsonNode & config);
-	static void loadArchive(const std::string &prefix, const std::string & mountPoint, const JsonNode & config, EResType::Type archiveType);
-	static void loadJsonMap(const std::string &prefix, const std::string & mountPoint, const JsonNode & config);
-
-	/**
-	 * Checks all subfolders of MODS directory for presence of mods
-	 * If this directory has mod.json file it will be added to resources
-	 */
-	static std::vector<std::string> getAvailableMods();
-	static void setActiveMods(std::vector<std::string> enabledMods); //WARNING: not reentrable. Do not call it twice!!!
-
-private:
-	/** Instance of resource loader */
-	static CResourceLoader * resourceLoader;
-	static CResourceLoader * initialLoader;
-};
-
-/**
- * A struct which describes the exact position of a resource.
- */
-class DLL_LINKAGE ResourceLocator
-{
-public:
-	/**
-	 * Ctor.
-	 *
-	 * @param archive A pointer to the resource archive object.
-	 * @param resourceName Unique resource name in the space of the given resource archive.
-	 */
-	ResourceLocator(ISimpleResourceLoader * loader, const std::string & resourceName);
-
-	/**
-	 * Gets a pointer to the resource loader object.
-	 *
-	 * @return a pointer to the resource loader object
-	 */
-	ISimpleResourceLoader * getLoader() const;
-
-	/**
-	 * Gets the resource name.
-	 *
-	 * @return the resource name.
-	 */
-	std::string getResourceName() const;
-
-private:
-	/**
-	 * A pointer to the loader which knows where and how to construct a stream object
-	 * which does the loading process actually.
-	 */
-	ISimpleResourceLoader * loader;
-
-	/** A unique name of the resource in space of the loader. */
-	std::string resourceName;
-};
-
-/**
- * A helper class which provides a functionality to convert extension strings to EResTypes.
- */
-class DLL_LINKAGE EResTypeHelper
-{
-public:
-	/**
-	 * Converts a extension string to a EResType enum object.
-	 *
-	 * @param extension The extension string e.g. .BMP, .PNG
-	 * @return Returns a EResType enum object
-	 */
-	static EResType::Type getTypeFromExtension(std::string extension);
-
-	/**
-	 * Gets the EResType as a string representation.
-	 *
-	 * @param type the EResType
-	 * @return the type as a string representation
-	 */
-	static std::string getEResTypeAsString(EResType::Type type);
-};

+ 316 - 0
lib/filesystem/Filesystem.cpp

@@ -0,0 +1,316 @@
+#include "StdInc.h"
+#include "Filesystem.h"
+
+#include "CFileInfo.h"
+
+#include "CArchiveLoader.h"
+#include "CFilesystemLoader.h"
+#include "AdapterLoaders.h"
+
+//For filesystem initialization
+#include "../JsonNode.h"
+#include "../GameConstants.h"
+#include "../VCMIDirs.h"
+#include "../CStopWatch.h"
+
+CFilesystemList * CResourceHandler::resourceLoader = nullptr;
+CFilesystemList * CResourceHandler::initialLoader = nullptr;
+
+ResourceID::ResourceID()
+	:type(EResType::OTHER)
+{
+}
+
+ResourceID::ResourceID(std::string name)
+{
+	CFileInfo info(std::move(name));
+	setName(info.getStem());
+	setType(info.getType());
+}
+
+ResourceID::ResourceID(std::string name, EResType::Type type)
+{
+	setName(std::move(name));
+	setType(type);
+}
+
+std::string ResourceID::getName() const
+{
+	return name;
+}
+
+EResType::Type ResourceID::getType() const
+{
+	return type;
+}
+
+void ResourceID::setName(std::string name)
+{
+	this->name = std::move(name);
+
+	size_t dotPos = this->name.find_last_of("/.");
+
+	if(dotPos != std::string::npos && this->name[dotPos] == '.')
+		this->name.erase(dotPos);
+
+	// strangely enough but this line takes 40-50% of filesystem loading time
+	boost::to_upper(this->name);
+}
+
+void ResourceID::setType(EResType::Type type)
+{
+	this->type = type;
+}
+
+void CResourceHandler::clear()
+{
+	delete resourceLoader;
+	delete initialLoader;
+}
+
+EResType::Type EResTypeHelper::getTypeFromExtension(std::string extension)
+{
+	boost::to_upper(extension);
+
+	static const std::map<std::string, EResType::Type> stringToRes =
+			boost::assign::map_list_of
+			(".TXT",   EResType::TEXT)
+			(".JSON",  EResType::TEXT)
+			(".DEF",   EResType::ANIMATION)
+			(".MSK",   EResType::MASK)
+			(".MSG",   EResType::MASK)
+			(".H3C",   EResType::CAMPAIGN)
+			(".H3M",   EResType::MAP)
+			(".FNT",   EResType::BMP_FONT)
+			(".TTF",   EResType::TTF_FONT)
+			(".BMP",   EResType::IMAGE)
+			(".JPG",   EResType::IMAGE)
+			(".PCX",   EResType::IMAGE)
+			(".PNG",   EResType::IMAGE)
+			(".TGA",   EResType::IMAGE)
+			(".WAV",   EResType::SOUND)
+			(".82M",   EResType::SOUND)
+			(".SMK",   EResType::VIDEO)
+			(".BIK",   EResType::VIDEO)
+			(".MJPG",  EResType::VIDEO)
+			(".MPG",   EResType::VIDEO)
+			(".AVI",   EResType::VIDEO)
+			(".MP3",   EResType::MUSIC)
+			(".OGG",   EResType::MUSIC)
+			(".LOD",   EResType::ARCHIVE_LOD)
+			(".PAC",   EResType::ARCHIVE_LOD)
+			(".VID",   EResType::ARCHIVE_VID)
+			(".SND",   EResType::ARCHIVE_SND)
+			(".PAL",   EResType::PALETTE)
+			(".VCGM1", EResType::CLIENT_SAVEGAME)
+			(".VSGM1", EResType::SERVER_SAVEGAME)
+			(".ERM",   EResType::ERM)
+			(".ERT",   EResType::ERT)
+			(".ERS",   EResType::ERS);
+
+	auto iter = stringToRes.find(extension);
+	if (iter == stringToRes.end())
+		return EResType::OTHER;
+	return iter->second;
+}
+
+std::string EResTypeHelper::getEResTypeAsString(EResType::Type type)
+{
+#define MAP_ENUM(value) (EResType::value, #value)
+
+	static const std::map<EResType::Type, std::string> stringToRes = boost::assign::map_list_of
+		MAP_ENUM(TEXT)
+		MAP_ENUM(ANIMATION)
+		MAP_ENUM(MASK)
+		MAP_ENUM(CAMPAIGN)
+		MAP_ENUM(MAP)
+		MAP_ENUM(BMP_FONT)
+		MAP_ENUM(TTF_FONT)
+		MAP_ENUM(IMAGE)
+		MAP_ENUM(VIDEO)
+		MAP_ENUM(SOUND)
+		MAP_ENUM(MUSIC)
+		MAP_ENUM(ARCHIVE_LOD)
+		MAP_ENUM(ARCHIVE_SND)
+		MAP_ENUM(ARCHIVE_VID)
+		MAP_ENUM(PALETTE)
+		MAP_ENUM(CLIENT_SAVEGAME)
+		MAP_ENUM(SERVER_SAVEGAME)
+		MAP_ENUM(DIRECTORY)
+		MAP_ENUM(ERM)
+		MAP_ENUM(ERT)
+		MAP_ENUM(ERS)
+		MAP_ENUM(OTHER);
+
+#undef MAP_ENUM
+
+	auto iter = stringToRes.find(type);
+	assert(iter != stringToRes.end());
+
+	return iter->second;
+}
+
+void CResourceHandler::initialize()
+{
+	//recurse only into specific directories
+	auto recurseInDir = [](std::string URI, int depth)
+	{
+		ResourceID ID(URI, EResType::DIRECTORY);
+
+		for(auto & loader : initialLoader->getResourcesWithName(ID))
+		{
+			auto filename = loader->getResourceName(ID);
+			if (filename)
+			{
+				auto dir = new CFilesystemLoader(URI + "/", *filename, depth, true);
+				initialLoader->addLoader(dir, false);
+			}
+		}
+	};
+
+	//temporary filesystem that will be used to initialize main one.
+	//used to solve several case-sensivity issues like Mp3 vs MP3
+	initialLoader = new CFilesystemList;
+	resourceLoader = new CFilesystemList;
+
+	for (auto & path : VCMIDirs::get().dataPaths())
+		initialLoader->addLoader(new CFilesystemLoader("", path, 0, true), false);
+
+	if (VCMIDirs::get().dataPaths().back() != VCMIDirs::get().userDataPath())
+		initialLoader->addLoader(new CFilesystemLoader("", VCMIDirs::get().userDataPath(), 0, true), false);
+
+	recurseInDir("CONFIG", 0);// look for configs
+	recurseInDir("DATA", 0); // look for archives
+	//TODO: improve mod loading process so depth 2 will no longer be needed
+	recurseInDir("MODS", 2); // look for mods. Depth 2 is required for now but won't cause speed issues if no mods present
+}
+
+void CResourceHandler::loadDirectory(const std::string &prefix, const std::string &mountPoint, const JsonNode & config)
+{
+	std::string URI = prefix + config["path"].String();
+	bool writeable = config["writeable"].Bool();
+	int depth = 16;
+	if (!config["depth"].isNull())
+		depth = config["depth"].Float();
+
+	ResourceID resID(URI, EResType::DIRECTORY);
+
+	for(auto & loader : initialLoader->getResourcesWithName(resID))
+	{
+		auto filename = loader->getResourceName(resID);
+		resourceLoader->addLoader(new CFilesystemLoader(mountPoint, *filename, depth), writeable);
+	}
+}
+
+void CResourceHandler::loadArchive(const std::string &prefix, const std::string &mountPoint, const JsonNode & config, EResType::Type archiveType)
+{
+	std::string URI = prefix + config["path"].String();
+	auto filename = initialLoader->getResourceName(ResourceID(URI, archiveType));
+	if (filename)
+		resourceLoader->addLoader(new CArchiveLoader(mountPoint, *filename), false);
+}
+
+void CResourceHandler::loadJsonMap(const std::string &prefix, const std::string &mountPoint, const JsonNode & config)
+{
+	std::string URI = prefix + config["path"].String();
+	auto filename = initialLoader->getResourceName(ResourceID(URI, EResType::TEXT));
+	if (filename)
+	{
+		auto configData = initialLoader->load(ResourceID(URI, EResType::TEXT))->readAll();
+		const JsonNode config((char*)configData.first.get(), configData.second);
+		resourceLoader->addLoader(new CMappedFileLoader(mountPoint, config), false);
+	}
+}
+
+void CResourceHandler::loadMainFileSystem(const std::string &fsConfigURI)
+{
+	auto fsConfigData = initialLoader->load(ResourceID(fsConfigURI, EResType::TEXT))->readAll();
+
+	const JsonNode fsConfig((char*)fsConfigData.first.get(), fsConfigData.second);
+
+	loadModFileSystem("", fsConfig["filesystem"]);
+
+	// hardcoded system-specific path, may not be inside any of data directories
+	resourceLoader->addLoader(new CFilesystemLoader("SAVES/", VCMIDirs::get().userSavePath()), true);
+	resourceLoader->addLoader(new CFilesystemLoader("CONFIG/", VCMIDirs::get().userConfigPath()), true);
+}
+
+void CResourceHandler::loadModFileSystem(const std::string & prefix, const JsonNode &fsConfig)
+{
+	for(auto & mountPoint : fsConfig.Struct())
+	{
+		for(auto & entry : mountPoint.second.Vector())
+		{
+			CStopWatch timer;
+			logGlobal->debugStream() << "\t\tLoading resource at " << prefix + entry["path"].String();
+
+			//TODO: replace if's with string->functor map?
+			if (entry["type"].String() == "map")
+				loadJsonMap(prefix, mountPoint.first, entry);
+			if (entry["type"].String() == "dir")
+				loadDirectory(prefix, mountPoint.first, entry);
+			if (entry["type"].String() == "lod")
+				loadArchive(prefix, mountPoint.first, entry, EResType::ARCHIVE_LOD);
+			if (entry["type"].String() == "snd")
+				loadArchive(prefix, mountPoint.first, entry, EResType::ARCHIVE_SND);
+			if (entry["type"].String() == "vid")
+				loadArchive(prefix, mountPoint.first, entry, EResType::ARCHIVE_VID);
+
+			logGlobal->debugStream() << "Resource loaded in " << timer.getDiff() << " ms.";
+		}
+	}
+}
+
+std::vector<std::string> CResourceHandler::getAvailableMods()
+{
+	static const std::string modDir = "MODS/";
+
+	auto list = initialLoader->getFilteredFiles([](const ResourceID & id) ->  bool
+	{
+		return id.getType() == EResType::DIRECTORY
+			&& boost::range::count(id.getName(), '/') == 1
+			&& boost::algorithm::starts_with(id.getName(), modDir);
+	});
+
+	//storage for found mods
+	std::vector<std::string> foundMods;
+	for (auto & entry : list)
+	{
+		std::string name = entry.getName();
+
+		name.erase(0, modDir.size()); //Remove path prefix
+
+		if (name == "WOG") // check if wog is actually present. Hack-ish but better than crash
+		{
+			if (!initialLoader->existsResource(ResourceID("DATA/ZVS", EResType::DIRECTORY)) &&
+				!initialLoader->existsResource(ResourceID("MODS/WOG/DATA/ZVS", EResType::DIRECTORY)))
+			{
+				continue;
+			}
+		}
+		foundMods.push_back(name);
+	}
+	return foundMods;
+}
+
+void CResourceHandler::setActiveMods(std::vector<std::string> enabledMods)
+{
+	// default FS config for mods: directory "Content" that acts as H3 root directory
+	JsonNode defaultFS;
+
+	defaultFS[""].Vector().resize(1);
+	defaultFS[""].Vector()[0]["type"].String() = "dir";
+	defaultFS[""].Vector()[0]["path"].String() = "/Content";
+
+	for(std::string & modName : enabledMods)
+	{
+		ResourceID modConfFile("mods/" + modName + "/mod", EResType::TEXT);
+		auto fsConfigData = initialLoader->load(modConfFile)->readAll();
+		const JsonNode fsConfig((char*)fsConfigData.first.get(), fsConfigData.second);
+
+		if (!fsConfig["filesystem"].isNull())
+			loadModFileSystem("mods/" + modName, fsConfig["filesystem"]);
+		else
+			loadModFileSystem("mods/" + modName, defaultFS);
+	}
+}

+ 208 - 0
lib/filesystem/Filesystem.h

@@ -0,0 +1,208 @@
+#pragma once
+
+/*
+ * Filesystem.h, 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 "CInputStream.h"
+#include "ISimpleResourceLoader.h"
+
+class CFilesystemList;
+class JsonNode;
+
+/**
+ * Specifies the resource type.
+ *
+ * Supported file extensions:
+ *
+ * Text: .txt .json
+ * Animation: .def
+ * Mask: .msk .msg
+ * Campaign: .h3c
+ * Map: .h3m
+ * Font: .fnt
+ * Image: .bmp, .jpg, .pcx, .png, .tga
+ * Sound: .wav .82m
+ * Video: .smk, .bik .mjpg .mpg
+ * Music: .mp3, .ogg
+ * Archive: .lod, .snd, .vid .pac
+ * Palette: .pal
+ * Savegame: .v*gm1
+ */
+namespace EResType
+{
+	enum Type
+	{
+		TEXT,
+		ANIMATION,
+		MASK,
+		CAMPAIGN,
+		MAP,
+		BMP_FONT,
+		TTF_FONT,
+		IMAGE,
+		VIDEO,
+		SOUND,
+		MUSIC,
+		ARCHIVE_VID,
+		ARCHIVE_SND,
+		ARCHIVE_LOD,
+		PALETTE,
+		CLIENT_SAVEGAME,
+		SERVER_SAVEGAME,
+		DIRECTORY,
+		ERM,
+		ERT,
+		ERS,
+		OTHER
+	};
+}
+
+/**
+ * A struct which identifies a resource clearly.
+ */
+class DLL_LINKAGE ResourceID
+{
+public:
+	/**
+	 * Default c-tor.
+	 */
+	ResourceID();
+
+	/**
+	 * Ctor. Can be used to create indentifier for resource loading using one parameter
+	 *
+	 * @param name The resource name including extension.
+	 */
+	explicit ResourceID(std::string fullName);
+
+	/**
+	 * Ctor.
+	 *
+	 * @param name The resource name.
+	 * @param type The resource type. A constant from the enumeration EResType.
+	 */
+	ResourceID(std::string name, EResType::Type type);
+
+	/**
+	 * Compares this object with a another resource identifier.
+	 *
+	 * @param other The other resource identifier.
+	 * @return Returns true if both are equally, false if not.
+	 */
+	inline bool operator==(ResourceID const & other) const
+	{
+		return name == other.name && type == other.type;
+	}
+
+	std::string getName() const;
+	EResType::Type getType() const;
+	void setName(std::string name);
+	void setType(EResType::Type type);
+
+private:
+	/** Specifies the resource name. No extension so .pcx and .png can override each other, always in upper case. **/
+	std::string name;
+
+	/**
+	 * Specifies the resource type. EResType::OTHER if not initialized.
+	 * Required to prevent conflicts if files with different types (e.g. text and image) have the same name.
+	 */
+	EResType::Type type;
+};
+
+namespace std
+{
+	template <> struct hash<ResourceID>
+	{
+		size_t operator()(const ResourceID & resourceIdent) const
+		{
+			std::hash<int> intHasher;
+			std::hash<std::string> stringHasher;
+			return stringHasher(resourceIdent.getName()) ^ intHasher(static_cast<int>(resourceIdent.getType()));
+		}
+	};
+}
+
+/**
+ * This class has static methods for a global resource loader access.
+ *
+ * Class is not thread-safe. Make sure nobody is calling getInstance while somebody else is calling initialize.
+ */
+class DLL_LINKAGE CResourceHandler
+{
+public:
+	/**
+	 * Gets an instance of resource loader.
+	 *
+	 * Make sure that you've set an instance before using it. It'll throw an exception if no instance was set.
+	 *
+	 * @return Returns an instance of resource loader.
+	 */
+	static ISimpleResourceLoader * get();
+
+	/**
+	 * Creates instance of resource loader.
+	 * Will not fill filesystem with data
+	 *
+	 */
+	static void initialize();
+
+	/**
+	 * Semi-debug method to track all possible cases of memory leaks
+	 * Used before exiting application
+	 *
+	 */
+	static void clear();
+
+	/**
+	 * Will load all filesystem data from Json data at this path (config/filesystem.json)
+	 * @param prefix - prefix for all paths in filesystem config
+	 */
+	static void loadMainFileSystem(const std::string & fsConfigURI);
+	static void loadModFileSystem(const std::string &prefix, const JsonNode & fsConfig);
+	static void loadDirectory(const std::string &prefix, const std::string & mountPoint, const JsonNode & config);
+	static void loadArchive(const std::string &prefix, const std::string & mountPoint, const JsonNode & config, EResType::Type archiveType);
+	static void loadJsonMap(const std::string &prefix, const std::string & mountPoint, const JsonNode & config);
+
+	/**
+	 * Checks all subfolders of MODS directory for presence of mods
+	 * If this directory has mod.json file it will be added to resources
+	 */
+	static std::vector<std::string> getAvailableMods();
+	static void setActiveMods(std::vector<std::string> enabledMods); //WARNING: not reentrable. Do not call it twice!!!
+
+private:
+	/** Instance of resource loader */
+	static CFilesystemList * resourceLoader;
+	static CFilesystemList * initialLoader;
+};
+
+/**
+ * A helper class which provides a functionality to convert extension strings to EResTypes.
+ */
+class DLL_LINKAGE EResTypeHelper
+{
+public:
+	/**
+	 * Converts a extension string to a EResType enum object.
+	 *
+	 * @param extension The extension string e.g. .BMP, .PNG
+	 * @return Returns a EResType enum object
+	 */
+	static EResType::Type getTypeFromExtension(std::string extension);
+
+	/**
+	 * Gets the EResType as a string representation.
+	 *
+	 * @param type the EResType
+	 * @return the type as a string representation
+	 */
+	static std::string getEResTypeAsString(EResType::Type type);
+};

+ 33 - 22
lib/filesystem/ISimpleResourceLoader.h

@@ -1,3 +1,4 @@
+#pragma once
 
 /*
  * ISimpleResourceLoader.h, part of VCMI engine
@@ -9,10 +10,8 @@
  *
  */
 
-#pragma once
-
-#include "CInputStream.h"
-#include "CResourceLoader.h" //FIXME: move ResourceID + EResType in separate file?
+class CInputStream;
+class ResourceID;
 
 /**
  * A class which knows the files containing in the archive or system and how to load them.
@@ -20,9 +19,6 @@
 class DLL_LINKAGE ISimpleResourceLoader
 {
 public:
-	/**
-	 * Dtor.
-	 */
 	virtual ~ISimpleResourceLoader() { };
 
 	/**
@@ -31,44 +27,59 @@ public:
 	 * @param resourceName The unqiue resource name in space of the archive.
 	 * @return a input stream object
 	 */
-	virtual std::unique_ptr<CInputStream> load(const std::string & resourceName) const =0;
+	virtual std::unique_ptr<CInputStream> load(const ResourceID & resourceName) const = 0;
 
 	/**
 	 * Checks if the entry exists.
 	 *
 	 * @return Returns true if the entry exists, false if not.
 	 */
-	virtual bool existsEntry(const std::string & resourceName) const =0;
+	virtual bool existsResource(const ResourceID & resourceName) const = 0;
 
 	/**
-	 * Gets all entries in the archive or (file) system.
+	 * Gets mount point to which this loader was attached
 	 *
-	 * @return Returns a list of all entries in the archive or (file) system.
+	 * @return mount point URI
 	 */
-	virtual std::unordered_map<ResourceID, std::string> getEntries() const =0;
+	virtual std::string getMountPoint() const = 0;
 
 	/**
-	 * Gets the origin of the loader.
+	 * Gets full name of resource, e.g. name of file in filesystem.
 	 *
-	 * @return the file path to source of this loader
+	 * @return path or empty optional if file can't be accessed independently (e.g. file in archive)
 	 */
-	virtual std::string getOrigin() const =0;
+	virtual boost::optional<std::string> getResourceName(const ResourceID & resourceName) const
+	{
+		return boost::optional<std::string>();
+	}
 
 	/**
-	 * Gets full name of resource, e.g. name of file in filesystem.
+	 * Get list of files that matches filter function
+	 *
+	 * @param filter Filter that returns true if specified ID matches filter
+	 * @return Returns list of flies
 	 */
-	virtual std::string getFullName(const std::string & resourceName) const
-	{
-		return getOrigin() + '/' + resourceName;
-	}
+	virtual std::unordered_set<ResourceID> getFilteredFiles(std::function<bool(const ResourceID &)> filter) const = 0;
 
 	/**
 	 * Creates new resource with specified filename.
 	 *
-	 * @returns true if new file was created, false on error or if file already exists
+	 * @return true if new file was created, false on error or if file already exists
 	 */
-	virtual bool createEntry(std::string filename, bool update = false)
+	virtual bool createResource(std::string filename, bool update = false)
 	{
 		return false;
 	}
+
+	/**
+	 * @brief Returns all loaders that have resource with such name
+	 *
+	 * @return vector with all loaders
+	 */
+	virtual std::vector<const ISimpleResourceLoader *> getResourcesWithName(const ResourceID & resourceName) const
+	{
+		if (existsResource(resourceName))
+			return std::vector<const ISimpleResourceLoader *>(1, this);
+		return std::vector<const ISimpleResourceLoader *>();
+	}
 };

+ 1 - 1
lib/mapping/CCampaignHandler.cpp

@@ -1,7 +1,7 @@
 #include "StdInc.h"
 #include "CCampaignHandler.h"
 
-#include "../filesystem/CResourceLoader.h"
+#include "../filesystem/Filesystem.h"
 #include "../filesystem/CCompressedStream.h"
 #include "../VCMI_Lib.h"
 #include "../vcmi_endian.h"

+ 1 - 1
lib/mapping/CMapEditManager.cpp

@@ -2,7 +2,7 @@
 #include "CMapEditManager.h"
 
 #include "../JsonNode.h"
-#include "../filesystem/CResourceLoader.h"
+#include "../filesystem/Filesystem.h"
 #include "../CDefObjInfoHandler.h"
 
 MapRect::MapRect() : x(0), y(0), z(0), width(0), height(0)

+ 1 - 1
lib/mapping/CMapService.cpp

@@ -1,7 +1,7 @@
 #include "StdInc.h"
 #include "CMapService.h"
 
-#include "../filesystem/CResourceLoader.h"
+#include "../filesystem/Filesystem.h"
 #include "../filesystem/CBinaryReader.h"
 #include "../filesystem/CCompressedStream.h"
 #include "../filesystem/CMemoryStream.h"

+ 1 - 2
lib/mapping/MapFormatH3M.cpp

@@ -15,8 +15,7 @@
 
 #include "../CStopWatch.h"
 
-#include "../filesystem/CResourceLoader.h"
-#include "../filesystem/CInputStream.h"
+#include "../filesystem/Filesystem.h"
 #include "CMap.h"
 
 #include "../CSpellHandler.h"

+ 1 - 1
lib/rmg/CMapGenerator.cpp

@@ -9,7 +9,7 @@
 #include "../CDefObjInfoHandler.h"
 #include "../CTownHandler.h"
 #include "../StringConstants.h"
-#include "../filesystem/CResourceLoader.h"
+#include "../filesystem/Filesystem.h"
 
 CMapGenOptions::CMapGenOptions() : width(CMapHeader::MAP_SIZE_MIDDLE), height(CMapHeader::MAP_SIZE_MIDDLE), hasTwoLevels(false),
 	playerCount(RANDOM_SIZE), teamCount(RANDOM_SIZE), compOnlyPlayerCount(0), compOnlyTeamCount(RANDOM_SIZE),

+ 3 - 3
server/CGameHandler.cpp

@@ -1,6 +1,6 @@
 #include "StdInc.h"
 
-#include "../lib/filesystem/CResourceLoader.h"
+#include "../lib/filesystem/Filesystem.h"
 #include "../lib/filesystem/CFileInfo.h"
 #include "../lib/int3.h"
 #include "../lib/mapping/CCampaignHandler.h"
@@ -2233,7 +2233,7 @@ void CGameHandler::save(const std::string & filename )
 // 		}
 
 		{
-			CSaveFile save(CResourceHandler::get()->getResourceName(ResourceID(info.getStem(), EResType::SERVER_SAVEGAME)));
+			CSaveFile save(*CResourceHandler::get()->getResourceName(ResourceID(info.getStem(), EResType::SERVER_SAVEGAME)));
 			saveCommonState(save);
             logGlobal->infoStream() << "Saving server state";
 			save << *this;
@@ -4533,7 +4533,7 @@ void CGameHandler::stackTurnTrigger(const CStack * st)
 		if (st->hasBonusOfType(Bonus::MANA_DRAIN) && !vstd::contains(st->state, EBattleStackState::DRAINED_MANA))
 		{
 			const CGHeroInstance * enemy = gs->curB->getHero(gs->curB->theOtherPlayer(st->owner));
-			const CGHeroInstance * owner = gs->curB->getHero(st->owner);
+			//const CGHeroInstance * owner = gs->curB->getHero(st->owner);
 			if (enemy)
 			{
 				ui32 manaDrained = st->valOfBonuses(Bonus::MANA_DRAIN);

+ 2 - 2
server/CVCMIServer.cpp

@@ -2,7 +2,7 @@
 
 #include <boost/asio.hpp>
 
-#include "../lib/filesystem/CResourceLoader.h"
+#include "../lib/filesystem/Filesystem.h"
 #include "../lib/mapping/CCampaignHandler.h"
 #include "../lib/CThreadHelper.h"
 #include "../lib/Connection.h"
@@ -475,7 +475,7 @@ void CVCMIServer::loadGame()
 // 	}
 
 	{
-		CLoadFile lf(CResourceHandler::get()->getResourceName(ResourceID(fname, EResType::SERVER_SAVEGAME)));
+		CLoadFile lf(*CResourceHandler::get()->getResourceName(ResourceID(fname, EResType::SERVER_SAVEGAME)));
 		gh.loadCommonState(lf);
 		lf >> gh;
 	}

+ 4 - 3
test/CMapEditManagerTest.cpp

@@ -14,8 +14,9 @@
 
 #include "../lib/mapping/CMapService.h"
 #include "../lib/mapping/CMap.h"
-#include "../lib/filesystem/CResourceLoader.h"
+#include "../lib/filesystem/Filesystem.h"
 #include "../lib/filesystem/CFilesystemLoader.h"
+#include "../lib/filesystem/AdapterLoaders.h"
 #include "../lib/JsonNode.h"
 #include "../lib/mapping/CMapEditManager.h"
 #include "../lib/int3.h"
@@ -81,8 +82,8 @@ BOOST_AUTO_TEST_CASE(CMapEditManager_DrawTerrain_View)
 	try
 	{
 		// Load maps and json config
-		auto loader = make_shared<CFilesystemLoader>(".");
-		CResourceHandler::get()->addLoader("test/", loader, false);
+		auto loader = new CFilesystemLoader("test/", ".");
+		dynamic_cast<CFilesystemList*>(CResourceHandler::get())->addLoader(loader, false);
 		const auto originalMap = CMapService::loadMap("test/TerrainViewTest");
 		auto map = CMapService::loadMap("test/TerrainViewTest");
 		logGlobal->infoStream() << "Loaded test map successfully.";