CModHandler.cpp 38 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226
  1. /*
  2. * CModHandler.cpp, part of VCMI engine
  3. *
  4. * Authors: listed in file AUTHORS in main folder
  5. *
  6. * License: GNU General Public License v2.0 or later
  7. * Full text of license available in license.txt file, in main folder
  8. *
  9. */
  10. #include "StdInc.h"
  11. #include "CModHandler.h"
  12. #include "rmg/CRmgTemplateStorage.h"
  13. #include "filesystem/AdapterLoaders.h"
  14. #include "filesystem/CFilesystemLoader.h"
  15. #include "filesystem/Filesystem.h"
  16. #include "CCreatureHandler.h"
  17. #include "CArtHandler.h"
  18. #include "CTownHandler.h"
  19. #include "CHeroHandler.h"
  20. #include "StringConstants.h"
  21. #include "CStopWatch.h"
  22. #include "IHandlerBase.h"
  23. #include "spells/CSpellHandler.h"
  24. #include "CSkillHandler.h"
  25. #include "CGeneralTextHandler.h"
  26. #include "Languages.h"
  27. #include "ScriptHandler.h"
  28. #include "RoadHandler.h"
  29. #include "GameSettings.h"
  30. #include "RiverHandler.h"
  31. #include "TerrainHandler.h"
  32. #include "BattleFieldHandler.h"
  33. #include "ObstacleHandler.h"
  34. #include "mapObjectConstructors/CObjectClassesHandler.h"
  35. #include <vstd/StringUtils.h>
  36. VCMI_LIB_NAMESPACE_BEGIN
  37. CIdentifierStorage::CIdentifierStorage():
  38. state(LOADING)
  39. {
  40. }
  41. void CIdentifierStorage::checkIdentifier(std::string & ID)
  42. {
  43. if (boost::algorithm::ends_with(ID, "."))
  44. logMod->warn("BIG WARNING: identifier %s seems to be broken!", ID);
  45. else
  46. {
  47. size_t pos = 0;
  48. do
  49. {
  50. if (std::tolower(ID[pos]) != ID[pos] ) //Not in camelCase
  51. {
  52. logMod->warn("Warning: identifier %s is not in camelCase!", ID);
  53. ID[pos] = std::tolower(ID[pos]);// Try to fix the ID
  54. }
  55. pos = ID.find('.', pos);
  56. }
  57. while(pos++ != std::string::npos);
  58. }
  59. }
  60. void CIdentifierStorage::requestIdentifier(ObjectCallback callback)
  61. {
  62. checkIdentifier(callback.type);
  63. checkIdentifier(callback.name);
  64. assert(!callback.localScope.empty());
  65. if (state != FINISHED) // enqueue request if loading is still in progress
  66. scheduledRequests.push_back(callback);
  67. else // execute immediately for "late" requests
  68. resolveIdentifier(callback);
  69. }
  70. CIdentifierStorage::ObjectCallback CIdentifierStorage::ObjectCallback::fromNameWithType(const std::string & scope, const std::string & fullName, const std::function<void(si32)> & callback, bool optional)
  71. {
  72. assert(!scope.empty());
  73. auto scopeAndFullName = vstd::splitStringToPair(fullName, ':');
  74. auto typeAndName = vstd::splitStringToPair(scopeAndFullName.second, '.');
  75. if (scope == scopeAndFullName.first)
  76. logMod->debug("Target scope for identifier '%s' is redundant! Identifier already defined in mod '%s'", fullName, scope);
  77. ObjectCallback result;
  78. result.localScope = scope;
  79. result.remoteScope = scopeAndFullName.first;
  80. result.type = typeAndName.first;
  81. result.name = typeAndName.second;
  82. result.callback = callback;
  83. result.optional = optional;
  84. return result;
  85. }
  86. CIdentifierStorage::ObjectCallback CIdentifierStorage::ObjectCallback::fromNameAndType(const std::string & scope, const std::string & type, const std::string & fullName, const std::function<void(si32)> & callback, bool optional)
  87. {
  88. assert(!scope.empty());
  89. auto scopeAndFullName = vstd::splitStringToPair(fullName, ':');
  90. auto typeAndName = vstd::splitStringToPair(scopeAndFullName.second, '.');
  91. if(!typeAndName.first.empty())
  92. {
  93. if (typeAndName.first != type)
  94. logMod->error("Identifier '%s' from mod '%s' requested with different type! Type '%s' expected!", fullName, scope, type);
  95. else
  96. logMod->debug("Target type for identifier '%s' defined in mod '%s' is redundant!", fullName, scope);
  97. }
  98. if (scope == scopeAndFullName.first)
  99. logMod->debug("Target scope for identifier '%s' is redundant! Identifier already defined in mod '%s'", fullName, scope);
  100. ObjectCallback result;
  101. result.localScope = scope;
  102. result.remoteScope = scopeAndFullName.first;
  103. result.type = type;
  104. result.name = typeAndName.second;
  105. result.callback = callback;
  106. result.optional = optional;
  107. return result;
  108. }
  109. void CIdentifierStorage::requestIdentifier(const std::string & scope, const std::string & type, const std::string & name, const std::function<void(si32)> & callback)
  110. {
  111. requestIdentifier(ObjectCallback::fromNameAndType(scope, type, name, callback, false));
  112. }
  113. void CIdentifierStorage::requestIdentifier(const std::string & scope, const std::string & fullName, const std::function<void(si32)> & callback)
  114. {
  115. requestIdentifier(ObjectCallback::fromNameWithType(scope, fullName, callback, false));
  116. }
  117. void CIdentifierStorage::requestIdentifier(const std::string & type, const JsonNode & name, const std::function<void(si32)> & callback)
  118. {
  119. requestIdentifier(ObjectCallback::fromNameAndType(name.meta, type, name.String(), callback, false));
  120. }
  121. void CIdentifierStorage::requestIdentifier(const JsonNode & name, const std::function<void(si32)> & callback)
  122. {
  123. requestIdentifier(ObjectCallback::fromNameWithType(name.meta, name.String(), callback, false));
  124. }
  125. void CIdentifierStorage::tryRequestIdentifier(const std::string & scope, const std::string & type, const std::string & name, const std::function<void(si32)> & callback)
  126. {
  127. requestIdentifier(ObjectCallback::fromNameAndType(scope, type, name, callback, true));
  128. }
  129. void CIdentifierStorage::tryRequestIdentifier(const std::string & type, const JsonNode & name, const std::function<void(si32)> & callback)
  130. {
  131. requestIdentifier(ObjectCallback::fromNameAndType(name.meta, type, name.String(), callback, true));
  132. }
  133. std::optional<si32> CIdentifierStorage::getIdentifier(const std::string & scope, const std::string & type, const std::string & name, bool silent)
  134. {
  135. auto idList = getPossibleIdentifiers(ObjectCallback::fromNameAndType(scope, type, name, std::function<void(si32)>(), silent));
  136. if (idList.size() == 1)
  137. return idList.front().id;
  138. if (!silent)
  139. logMod->error("Failed to resolve identifier %s of type %s from mod %s", name , type ,scope);
  140. return std::optional<si32>();
  141. }
  142. std::optional<si32> CIdentifierStorage::getIdentifier(const std::string & type, const JsonNode & name, bool silent)
  143. {
  144. auto idList = getPossibleIdentifiers(ObjectCallback::fromNameAndType(name.meta, type, name.String(), std::function<void(si32)>(), silent));
  145. if (idList.size() == 1)
  146. return idList.front().id;
  147. if (!silent)
  148. logMod->error("Failed to resolve identifier %s of type %s from mod %s", name.String(), type, name.meta);
  149. return std::optional<si32>();
  150. }
  151. std::optional<si32> CIdentifierStorage::getIdentifier(const JsonNode & name, bool silent)
  152. {
  153. auto idList = getPossibleIdentifiers(ObjectCallback::fromNameWithType(name.meta, name.String(), std::function<void(si32)>(), silent));
  154. if (idList.size() == 1)
  155. return idList.front().id;
  156. if (!silent)
  157. logMod->error("Failed to resolve identifier %s from mod %s", name.String(), name.meta);
  158. return std::optional<si32>();
  159. }
  160. std::optional<si32> CIdentifierStorage::getIdentifier(const std::string & scope, const std::string & fullName, bool silent)
  161. {
  162. auto idList = getPossibleIdentifiers(ObjectCallback::fromNameWithType(scope, fullName, std::function<void(si32)>(), silent));
  163. if (idList.size() == 1)
  164. return idList.front().id;
  165. if (!silent)
  166. logMod->error("Failed to resolve identifier %s from mod %s", fullName, scope);
  167. return std::optional<si32>();
  168. }
  169. void CIdentifierStorage::registerObject(const std::string & scope, const std::string & type, const std::string & name, si32 identifier)
  170. {
  171. ObjectData data;
  172. data.scope = scope;
  173. data.id = identifier;
  174. std::string fullID = type + '.' + name;
  175. checkIdentifier(fullID);
  176. std::pair<const std::string, ObjectData> mapping = std::make_pair(fullID, data);
  177. if(!vstd::containsMapping(registeredObjects, mapping))
  178. {
  179. logMod->trace("registered %s as %s:%s", fullID, scope, identifier);
  180. registeredObjects.insert(mapping);
  181. }
  182. }
  183. std::vector<CIdentifierStorage::ObjectData> CIdentifierStorage::getPossibleIdentifiers(const ObjectCallback & request)
  184. {
  185. std::set<std::string> allowedScopes;
  186. bool isValidScope = true;
  187. // called have not specified destination mod explicitly
  188. if (request.remoteScope.empty())
  189. {
  190. // special scope that should have access to all in-game objects
  191. if (request.localScope == CModHandler::scopeGame())
  192. {
  193. for(const auto & modName : VLC->modh->getActiveMods())
  194. allowedScopes.insert(modName);
  195. }
  196. // normally ID's from all required mods, own mod and virtual built-in mod are allowed
  197. else if(request.localScope != CModHandler::scopeBuiltin() && !request.localScope.empty())
  198. {
  199. allowedScopes = VLC->modh->getModDependencies(request.localScope, isValidScope);
  200. if(!isValidScope)
  201. return std::vector<ObjectData>();
  202. allowedScopes.insert(request.localScope);
  203. }
  204. // all mods can access built-in mod
  205. allowedScopes.insert(CModHandler::scopeBuiltin());
  206. }
  207. else
  208. {
  209. //if destination mod was specified explicitly, restrict lookup to this mod
  210. if(request.remoteScope == CModHandler::scopeBuiltin() )
  211. {
  212. //built-in mod is an implicit dependency for all mods, allow access into it
  213. allowedScopes.insert(request.remoteScope);
  214. }
  215. else if ( request.localScope == CModHandler::scopeGame() )
  216. {
  217. // allow access, this is special scope that should have access to all in-game objects
  218. allowedScopes.insert(request.remoteScope);
  219. }
  220. else if(request.remoteScope == request.localScope )
  221. {
  222. // allow self-access
  223. allowedScopes.insert(request.remoteScope);
  224. }
  225. else
  226. {
  227. // allow access only if mod is in our dependencies
  228. auto myDeps = VLC->modh->getModDependencies(request.localScope, isValidScope);
  229. if(!isValidScope)
  230. return std::vector<ObjectData>();
  231. if(myDeps.count(request.remoteScope))
  232. allowedScopes.insert(request.remoteScope);
  233. }
  234. }
  235. std::string fullID = request.type + '.' + request.name;
  236. auto entries = registeredObjects.equal_range(fullID);
  237. if (entries.first != entries.second)
  238. {
  239. std::vector<ObjectData> locatedIDs;
  240. for (auto it = entries.first; it != entries.second; it++)
  241. {
  242. if (vstd::contains(allowedScopes, it->second.scope))
  243. {
  244. locatedIDs.push_back(it->second);
  245. }
  246. }
  247. return locatedIDs;
  248. }
  249. return std::vector<ObjectData>();
  250. }
  251. bool CIdentifierStorage::resolveIdentifier(const ObjectCallback & request)
  252. {
  253. auto identifiers = getPossibleIdentifiers(request);
  254. if (identifiers.size() == 1) // normally resolved ID
  255. {
  256. request.callback(identifiers.front().id);
  257. return true;
  258. }
  259. if (request.optional && identifiers.empty()) // failed to resolve optinal ID
  260. {
  261. return true;
  262. }
  263. // error found. Try to generate some debug info
  264. if(identifiers.empty())
  265. logMod->error("Unknown identifier!");
  266. else
  267. logMod->error("Ambiguous identifier request!");
  268. logMod->error("Request for %s.%s from mod %s", request.type, request.name, request.localScope);
  269. for(const auto & id : identifiers)
  270. {
  271. logMod->error("\tID is available in mod %s", id.scope);
  272. }
  273. return false;
  274. }
  275. void CIdentifierStorage::finalize()
  276. {
  277. state = FINALIZING;
  278. bool errorsFound = false;
  279. while ( !scheduledRequests.empty() )
  280. {
  281. // Use local copy since new requests may appear during resolving, invalidating any iterators
  282. auto request = scheduledRequests.back();
  283. scheduledRequests.pop_back();
  284. if (!resolveIdentifier(request))
  285. errorsFound = true;
  286. }
  287. if (errorsFound)
  288. {
  289. for(const auto & object : registeredObjects)
  290. {
  291. logMod->trace("%s : %s -> %d", object.second.scope, object.first, object.second.id);
  292. }
  293. logMod->error("All known identifiers were dumped into log file");
  294. }
  295. assert(errorsFound == false);
  296. state = FINISHED;
  297. }
  298. ContentTypeHandler::ContentTypeHandler(IHandlerBase * handler, const std::string & objectName):
  299. handler(handler),
  300. objectName(objectName),
  301. originalData(handler->loadLegacyData())
  302. {
  303. for(auto & node : originalData)
  304. {
  305. node.setMeta(CModHandler::scopeBuiltin());
  306. }
  307. }
  308. bool ContentTypeHandler::preloadModData(const std::string & modName, const std::vector<std::string> & fileList, bool validate)
  309. {
  310. bool result = false;
  311. JsonNode data = JsonUtils::assembleFromFiles(fileList, result);
  312. data.setMeta(modName);
  313. ModInfo & modInfo = modData[modName];
  314. for(auto entry : data.Struct())
  315. {
  316. size_t colon = entry.first.find(':');
  317. if (colon == std::string::npos)
  318. {
  319. // normal object, local to this mod
  320. modInfo.modData[entry.first].swap(entry.second);
  321. }
  322. else
  323. {
  324. std::string remoteName = entry.first.substr(0, colon);
  325. std::string objectName = entry.first.substr(colon + 1);
  326. // patching this mod? Send warning and continue - this situation can be handled normally
  327. if (remoteName == modName)
  328. logMod->warn("Redundant namespace definition for %s", objectName);
  329. logMod->trace("Patching object %s (%s) from %s", objectName, remoteName, modName);
  330. JsonNode & remoteConf = modData[remoteName].patches[objectName];
  331. JsonUtils::merge(remoteConf, entry.second);
  332. }
  333. }
  334. return result;
  335. }
  336. bool ContentTypeHandler::loadMod(const std::string & modName, bool validate)
  337. {
  338. ModInfo & modInfo = modData[modName];
  339. bool result = true;
  340. auto performValidate = [&,this](JsonNode & data, const std::string & name){
  341. handler->beforeValidate(data);
  342. if (validate)
  343. result &= JsonUtils::validate(data, "vcmi:" + objectName, name);
  344. };
  345. // apply patches
  346. if (!modInfo.patches.isNull())
  347. JsonUtils::merge(modInfo.modData, modInfo.patches);
  348. for(auto & entry : modInfo.modData.Struct())
  349. {
  350. const std::string & name = entry.first;
  351. JsonNode & data = entry.second;
  352. if (data.meta != modName)
  353. logMod->warn("Mod %s is attempting to inject object %s into mod %s! This may not be supported in future versions!", data.meta, name, modName);
  354. if (vstd::contains(data.Struct(), "index") && !data["index"].isNull())
  355. {
  356. if (modName != "core")
  357. logMod->warn("Mod %s is attempting to load original data! This should be reserved for built-in mod.", modName);
  358. // try to add H3 object data
  359. size_t index = static_cast<size_t>(data["index"].Float());
  360. if(originalData.size() > index)
  361. {
  362. logMod->trace("found original data in loadMod(%s) at index %d", name, index);
  363. JsonUtils::merge(originalData[index], data);
  364. std::swap(originalData[index], data);
  365. originalData[index].clear(); // do not use same data twice (same ID)
  366. }
  367. else
  368. {
  369. logMod->trace("no original data in loadMod(%s) at index %d", name, index);
  370. }
  371. performValidate(data, name);
  372. handler->loadObject(modName, name, data, index);
  373. }
  374. else
  375. {
  376. // normal new object
  377. logMod->trace("no index in loadMod(%s)", name);
  378. performValidate(data,name);
  379. handler->loadObject(modName, name, data);
  380. }
  381. }
  382. return result;
  383. }
  384. void ContentTypeHandler::loadCustom()
  385. {
  386. handler->loadCustom();
  387. }
  388. void ContentTypeHandler::afterLoadFinalization()
  389. {
  390. handler->afterLoadFinalization();
  391. }
  392. void CContentHandler::init()
  393. {
  394. handlers.insert(std::make_pair("heroClasses", ContentTypeHandler(&VLC->heroh->classes, "heroClass")));
  395. handlers.insert(std::make_pair("artifacts", ContentTypeHandler(VLC->arth, "artifact")));
  396. handlers.insert(std::make_pair("creatures", ContentTypeHandler(VLC->creh, "creature")));
  397. handlers.insert(std::make_pair("factions", ContentTypeHandler(VLC->townh, "faction")));
  398. handlers.insert(std::make_pair("objects", ContentTypeHandler(VLC->objtypeh, "object")));
  399. handlers.insert(std::make_pair("heroes", ContentTypeHandler(VLC->heroh, "hero")));
  400. handlers.insert(std::make_pair("spells", ContentTypeHandler(VLC->spellh, "spell")));
  401. handlers.insert(std::make_pair("skills", ContentTypeHandler(VLC->skillh, "skill")));
  402. handlers.insert(std::make_pair("templates", ContentTypeHandler(VLC->tplh, "template")));
  403. #if SCRIPTING_ENABLED
  404. handlers.insert(std::make_pair("scripts", ContentTypeHandler(VLC->scriptHandler, "script")));
  405. #endif
  406. handlers.insert(std::make_pair("battlefields", ContentTypeHandler(VLC->battlefieldsHandler, "battlefield")));
  407. handlers.insert(std::make_pair("terrains", ContentTypeHandler(VLC->terrainTypeHandler, "terrain")));
  408. handlers.insert(std::make_pair("rivers", ContentTypeHandler(VLC->riverTypeHandler, "river")));
  409. handlers.insert(std::make_pair("roads", ContentTypeHandler(VLC->roadTypeHandler, "road")));
  410. handlers.insert(std::make_pair("obstacles", ContentTypeHandler(VLC->obstacleHandler, "obstacle")));
  411. //TODO: any other types of moddables?
  412. }
  413. bool CContentHandler::preloadModData(const std::string & modName, JsonNode modConfig, bool validate)
  414. {
  415. bool result = true;
  416. for(auto & handler : handlers)
  417. {
  418. result &= handler.second.preloadModData(modName, modConfig[handler.first].convertTo<std::vector<std::string> >(), validate);
  419. }
  420. return result;
  421. }
  422. bool CContentHandler::loadMod(const std::string & modName, bool validate)
  423. {
  424. bool result = true;
  425. for(auto & handler : handlers)
  426. {
  427. result &= handler.second.loadMod(modName, validate);
  428. }
  429. return result;
  430. }
  431. void CContentHandler::loadCustom()
  432. {
  433. for(auto & handler : handlers)
  434. {
  435. handler.second.loadCustom();
  436. }
  437. }
  438. void CContentHandler::afterLoadFinalization()
  439. {
  440. for(auto & handler : handlers)
  441. {
  442. handler.second.afterLoadFinalization();
  443. }
  444. }
  445. void CContentHandler::preloadData(CModInfo & mod)
  446. {
  447. bool validate = (mod.validation != CModInfo::PASSED);
  448. // print message in format [<8-symbols checksum>] <modname>
  449. logMod->info("\t\t[%08x]%s", mod.checksum, mod.name);
  450. if (validate && mod.identifier != CModHandler::scopeBuiltin())
  451. {
  452. if (!JsonUtils::validate(mod.config, "vcmi:mod", mod.identifier))
  453. mod.validation = CModInfo::FAILED;
  454. }
  455. if (!preloadModData(mod.identifier, mod.config, validate))
  456. mod.validation = CModInfo::FAILED;
  457. }
  458. void CContentHandler::load(CModInfo & mod)
  459. {
  460. bool validate = (mod.validation != CModInfo::PASSED);
  461. if (!loadMod(mod.identifier, validate))
  462. mod.validation = CModInfo::FAILED;
  463. if (validate)
  464. {
  465. if (mod.validation != CModInfo::FAILED)
  466. logMod->info("\t\t[DONE] %s", mod.name);
  467. else
  468. logMod->error("\t\t[FAIL] %s", mod.name);
  469. }
  470. else
  471. logMod->info("\t\t[SKIP] %s", mod.name);
  472. }
  473. const ContentTypeHandler & CContentHandler::operator[](const std::string & name) const
  474. {
  475. return handlers.at(name);
  476. }
  477. static JsonNode loadModSettings(const std::string & path)
  478. {
  479. if (CResourceHandler::get("local")->existsResource(ResourceID(path)))
  480. {
  481. return JsonNode(ResourceID(path, EResType::TEXT));
  482. }
  483. // Probably new install. Create initial configuration
  484. CResourceHandler::get("local")->createResource(path);
  485. return JsonNode();
  486. }
  487. JsonNode addMeta(JsonNode config, const std::string & meta)
  488. {
  489. config.setMeta(meta);
  490. return config;
  491. }
  492. CModInfo::CModInfo():
  493. checksum(0),
  494. explicitlyEnabled(false),
  495. implicitlyEnabled(true),
  496. validation(PENDING)
  497. {
  498. }
  499. CModInfo::CModInfo(const std::string & identifier, const JsonNode & local, const JsonNode & config):
  500. identifier(identifier),
  501. name(config["name"].String()),
  502. description(config["description"].String()),
  503. dependencies(config["depends"].convertTo<std::set<std::string>>()),
  504. conflicts(config["conflicts"].convertTo<std::set<std::string>>()),
  505. checksum(0),
  506. explicitlyEnabled(false),
  507. implicitlyEnabled(true),
  508. validation(PENDING),
  509. config(addMeta(config, identifier))
  510. {
  511. version = CModVersion::fromString(config["version"].String());
  512. if(!config["compatibility"].isNull())
  513. {
  514. vcmiCompatibleMin = CModVersion::fromString(config["compatibility"]["min"].String());
  515. vcmiCompatibleMax = CModVersion::fromString(config["compatibility"]["max"].String());
  516. }
  517. if (!config["language"].isNull())
  518. baseLanguage = config["language"].String();
  519. else
  520. baseLanguage = "english";
  521. loadLocalData(local);
  522. }
  523. JsonNode CModInfo::saveLocalData() const
  524. {
  525. std::ostringstream stream;
  526. stream << std::noshowbase << std::hex << std::setw(8) << std::setfill('0') << checksum;
  527. JsonNode conf;
  528. conf["active"].Bool() = explicitlyEnabled;
  529. conf["validated"].Bool() = validation != FAILED;
  530. conf["checksum"].String() = stream.str();
  531. return conf;
  532. }
  533. std::string CModInfo::getModDir(const std::string & name)
  534. {
  535. return "MODS/" + boost::algorithm::replace_all_copy(name, ".", "/MODS/");
  536. }
  537. std::string CModInfo::getModFile(const std::string & name)
  538. {
  539. return getModDir(name) + "/mod.json";
  540. }
  541. void CModInfo::updateChecksum(ui32 newChecksum)
  542. {
  543. // comment-out next line to force validation of all mods ignoring checksum
  544. if (newChecksum != checksum)
  545. {
  546. checksum = newChecksum;
  547. validation = PENDING;
  548. }
  549. }
  550. void CModInfo::loadLocalData(const JsonNode & data)
  551. {
  552. bool validated = false;
  553. implicitlyEnabled = true;
  554. explicitlyEnabled = !config["keepDisabled"].Bool();
  555. checksum = 0;
  556. if (data.getType() == JsonNode::JsonType::DATA_BOOL)
  557. {
  558. explicitlyEnabled = data.Bool();
  559. }
  560. if (data.getType() == JsonNode::JsonType::DATA_STRUCT)
  561. {
  562. explicitlyEnabled = data["active"].Bool();
  563. validated = data["validated"].Bool();
  564. checksum = strtol(data["checksum"].String().c_str(), nullptr, 16);
  565. }
  566. //check compatibility
  567. implicitlyEnabled &= (vcmiCompatibleMin.isNull() || CModVersion::GameVersion().compatible(vcmiCompatibleMin));
  568. implicitlyEnabled &= (vcmiCompatibleMax.isNull() || vcmiCompatibleMax.compatible(CModVersion::GameVersion()));
  569. if(!implicitlyEnabled)
  570. logGlobal->warn("Mod %s is incompatible with current version of VCMI and cannot be enabled", name);
  571. if (boost::iequals(config["modType"].String(), "translation")) // compatibility code - mods use "Translation" type at the moment
  572. {
  573. if (baseLanguage != VLC->generaltexth->getPreferredLanguage())
  574. {
  575. logGlobal->warn("Translation mod %s was not loaded: language mismatch!", name);
  576. implicitlyEnabled = false;
  577. }
  578. }
  579. if (isEnabled())
  580. validation = validated ? PASSED : PENDING;
  581. else
  582. validation = validated ? PASSED : FAILED;
  583. }
  584. bool CModInfo::isEnabled() const
  585. {
  586. return implicitlyEnabled && explicitlyEnabled;
  587. }
  588. void CModInfo::setEnabled(bool on)
  589. {
  590. explicitlyEnabled = on;
  591. }
  592. CModHandler::CModHandler() : content(std::make_shared<CContentHandler>())
  593. {
  594. //TODO: moddable spell schools
  595. for (auto i = 0; i < GameConstants::DEFAULT_SCHOOLS; ++i)
  596. identifiers.registerObject(CModHandler::scopeBuiltin(), "spellSchool", SpellConfig::SCHOOL[i].jsonName, SpellConfig::SCHOOL[i].id);
  597. identifiers.registerObject(CModHandler::scopeBuiltin(), "spellSchool", "any", SpellSchool(ESpellSchool::ANY));
  598. for (int i = 0; i < GameConstants::RESOURCE_QUANTITY; ++i)
  599. {
  600. identifiers.registerObject(CModHandler::scopeBuiltin(), "resource", GameConstants::RESOURCE_NAMES[i], i);
  601. }
  602. for(int i=0; i<GameConstants::PRIMARY_SKILLS; ++i)
  603. {
  604. identifiers.registerObject(CModHandler::scopeBuiltin(), "primSkill", PrimarySkill::names[i], i);
  605. identifiers.registerObject(CModHandler::scopeBuiltin(), "primarySkill", PrimarySkill::names[i], i);
  606. }
  607. }
  608. // currentList is passed by value to get current list of depending mods
  609. bool CModHandler::hasCircularDependency(const TModID & modID, std::set<TModID> currentList) const
  610. {
  611. const CModInfo & mod = allMods.at(modID);
  612. // Mod already present? We found a loop
  613. if (vstd::contains(currentList, modID))
  614. {
  615. logMod->error("Error: Circular dependency detected! Printing dependency list:");
  616. logMod->error("\t%s -> ", mod.name);
  617. return true;
  618. }
  619. currentList.insert(modID);
  620. // recursively check every dependency of this mod
  621. for(const TModID & dependency : mod.dependencies)
  622. {
  623. if (hasCircularDependency(dependency, currentList))
  624. {
  625. logMod->error("\t%s ->\n", mod.name); // conflict detected, print dependency list
  626. return true;
  627. }
  628. }
  629. return false;
  630. }
  631. // Returned vector affects the resource loaders call order (see CFilesystemList::load).
  632. // The loaders call order matters when dependent mod overrides resources in its dependencies.
  633. std::vector <TModID> CModHandler::validateAndSortDependencies(std::vector <TModID> modsToResolve) const
  634. {
  635. // Topological sort algorithm.
  636. // TODO: Investigate possible ways to improve performance.
  637. boost::range::sort(modsToResolve); // Sort mods per name
  638. std::vector <TModID> sortedValidMods; // Vector keeps order of elements (LIFO)
  639. sortedValidMods.reserve(modsToResolve.size()); // push_back calls won't cause memory reallocation
  640. std::set <TModID> resolvedModIDs; // Use a set for validation for performance reason, but set does not keep order of elements
  641. // Mod is resolved if it has not dependencies or all its dependencies are already resolved
  642. auto isResolved = [&](const CModInfo & mod) -> CModInfo::EValidationStatus
  643. {
  644. if(mod.dependencies.size() > resolvedModIDs.size())
  645. return CModInfo::PENDING;
  646. for(const TModID & dependency : mod.dependencies)
  647. {
  648. if(!vstd::contains(resolvedModIDs, dependency))
  649. return CModInfo::PENDING;
  650. }
  651. return CModInfo::PASSED;
  652. };
  653. while(true)
  654. {
  655. std::set <TModID> resolvedOnCurrentTreeLevel;
  656. for(auto it = modsToResolve.begin(); it != modsToResolve.end();) // One iteration - one level of mods tree
  657. {
  658. if(isResolved(allMods.at(*it)) == CModInfo::PASSED)
  659. {
  660. resolvedOnCurrentTreeLevel.insert(*it); // Not to the resolvedModIDs, so current node childs will be resolved on the next iteration
  661. sortedValidMods.push_back(*it);
  662. it = modsToResolve.erase(it);
  663. continue;
  664. }
  665. it++;
  666. }
  667. if(!resolvedOnCurrentTreeLevel.empty())
  668. {
  669. resolvedModIDs.insert(resolvedOnCurrentTreeLevel.begin(), resolvedOnCurrentTreeLevel.end());
  670. continue;
  671. }
  672. // If there're no valid mods on the current mods tree level, no more mod can be resolved, should be end.
  673. break;
  674. }
  675. // Left mods have unresolved dependencies, output all to log.
  676. for(const auto & brokenModID : modsToResolve)
  677. {
  678. const CModInfo & brokenMod = allMods.at(brokenModID);
  679. for(const TModID & dependency : brokenMod.dependencies)
  680. {
  681. if(!vstd::contains(resolvedModIDs, dependency))
  682. logMod->error("Mod '%s' will not work: it depends on mod '%s', which is not installed.", brokenMod.name, dependency);
  683. }
  684. }
  685. return sortedValidMods;
  686. }
  687. std::vector<std::string> CModHandler::getModList(const std::string & path) const
  688. {
  689. std::string modDir = boost::to_upper_copy(path + "MODS/");
  690. size_t depth = boost::range::count(modDir, '/');
  691. auto list = CResourceHandler::get("initial")->getFilteredFiles([&](const ResourceID & id) -> bool
  692. {
  693. if (id.getType() != EResType::DIRECTORY)
  694. return false;
  695. if (!boost::algorithm::starts_with(id.getName(), modDir))
  696. return false;
  697. if (boost::range::count(id.getName(), '/') != depth )
  698. return false;
  699. return true;
  700. });
  701. //storage for found mods
  702. std::vector<std::string> foundMods;
  703. for(const auto & entry : list)
  704. {
  705. std::string name = entry.getName();
  706. name.erase(0, modDir.size()); //Remove path prefix
  707. if (!name.empty())
  708. foundMods.push_back(name);
  709. }
  710. return foundMods;
  711. }
  712. bool CModHandler::isScopeReserved(const TModID & scope)
  713. {
  714. //following scopes are reserved - either in use by mod system or by filesystem
  715. static const std::array<TModID, 9> reservedScopes = {
  716. "core", "map", "game", "root", "saves", "config", "local", "initial", "mapEditor"
  717. };
  718. return std::find(reservedScopes.begin(), reservedScopes.end(), scope) != reservedScopes.end();
  719. }
  720. const TModID & CModHandler::scopeBuiltin()
  721. {
  722. static const TModID scope = "core";
  723. return scope;
  724. }
  725. const TModID & CModHandler::scopeGame()
  726. {
  727. static const TModID scope = "game";
  728. return scope;
  729. }
  730. const TModID & CModHandler::scopeMap()
  731. {
  732. //TODO: implement accessing map dependencies for both H3 and VCMI maps
  733. // for now, allow access to any identifiers
  734. static const TModID scope = "game";
  735. return scope;
  736. }
  737. void CModHandler::loadMods(const std::string & path, const std::string & parent, const JsonNode & modSettings, bool enableMods)
  738. {
  739. for(const std::string & modName : getModList(path))
  740. loadOneMod(modName, parent, modSettings, enableMods);
  741. }
  742. void CModHandler::loadOneMod(std::string modName, const std::string & parent, const JsonNode & modSettings, bool enableMods)
  743. {
  744. boost::to_lower(modName);
  745. std::string modFullName = parent.empty() ? modName : parent + '.' + modName;
  746. if ( isScopeReserved(modFullName))
  747. {
  748. logMod->error("Can not load mod %s - this name is reserved for internal use!", modFullName);
  749. return;
  750. }
  751. if(CResourceHandler::get("initial")->existsResource(ResourceID(CModInfo::getModFile(modFullName))))
  752. {
  753. CModInfo mod(modFullName, modSettings[modName], JsonNode(ResourceID(CModInfo::getModFile(modFullName))));
  754. if (!parent.empty()) // this is submod, add parent to dependencies
  755. mod.dependencies.insert(parent);
  756. allMods[modFullName] = mod;
  757. if (mod.isEnabled() && enableMods)
  758. activeMods.push_back(modFullName);
  759. loadMods(CModInfo::getModDir(modFullName) + '/', modFullName, modSettings[modName]["mods"], enableMods && mod.isEnabled());
  760. }
  761. }
  762. void CModHandler::loadMods(bool onlyEssential)
  763. {
  764. JsonNode modConfig;
  765. if(onlyEssential)
  766. {
  767. loadOneMod("vcmi", "", modConfig, true);//only vcmi and submods
  768. }
  769. else
  770. {
  771. modConfig = loadModSettings("config/modSettings.json");
  772. loadMods("", "", modConfig["activeMods"], true);
  773. }
  774. coreMod = CModInfo(CModHandler::scopeBuiltin(), modConfig[CModHandler::scopeBuiltin()], JsonNode(ResourceID("config/gameConfig.json")));
  775. coreMod.name = "Original game files";
  776. }
  777. std::vector<std::string> CModHandler::getAllMods()
  778. {
  779. std::vector<std::string> modlist;
  780. modlist.reserve(allMods.size());
  781. for (auto & entry : allMods)
  782. modlist.push_back(entry.first);
  783. return modlist;
  784. }
  785. std::vector<std::string> CModHandler::getActiveMods()
  786. {
  787. return activeMods;
  788. }
  789. const CModInfo & CModHandler::getModInfo(const TModID & modId) const
  790. {
  791. return allMods.at(modId);
  792. }
  793. static JsonNode genDefaultFS()
  794. {
  795. // default FS config for mods: directory "Content" that acts as H3 root directory
  796. JsonNode defaultFS;
  797. defaultFS[""].Vector().resize(2);
  798. defaultFS[""].Vector()[0]["type"].String() = "zip";
  799. defaultFS[""].Vector()[0]["path"].String() = "/Content.zip";
  800. defaultFS[""].Vector()[1]["type"].String() = "dir";
  801. defaultFS[""].Vector()[1]["path"].String() = "/Content";
  802. return defaultFS;
  803. }
  804. static ISimpleResourceLoader * genModFilesystem(const std::string & modName, const JsonNode & conf)
  805. {
  806. static const JsonNode defaultFS = genDefaultFS();
  807. if (!conf["filesystem"].isNull())
  808. return CResourceHandler::createFileSystem(CModInfo::getModDir(modName), conf["filesystem"]);
  809. else
  810. return CResourceHandler::createFileSystem(CModInfo::getModDir(modName), defaultFS);
  811. }
  812. static ui32 calculateModChecksum(const std::string & modName, ISimpleResourceLoader * filesystem)
  813. {
  814. boost::crc_32_type modChecksum;
  815. // first - add current VCMI version into checksum to force re-validation on VCMI updates
  816. modChecksum.process_bytes(reinterpret_cast<const void*>(GameConstants::VCMI_VERSION.data()), GameConstants::VCMI_VERSION.size());
  817. // second - add mod.json into checksum because filesystem does not contains this file
  818. // FIXME: remove workaround for core mod
  819. if (modName != CModHandler::scopeBuiltin())
  820. {
  821. ResourceID modConfFile(CModInfo::getModFile(modName), EResType::TEXT);
  822. ui32 configChecksum = CResourceHandler::get("initial")->load(modConfFile)->calculateCRC32();
  823. modChecksum.process_bytes(reinterpret_cast<const void *>(&configChecksum), sizeof(configChecksum));
  824. }
  825. // third - add all detected text files from this mod into checksum
  826. auto files = filesystem->getFilteredFiles([](const ResourceID & resID)
  827. {
  828. return resID.getType() == EResType::TEXT &&
  829. ( boost::starts_with(resID.getName(), "DATA") ||
  830. boost::starts_with(resID.getName(), "CONFIG"));
  831. });
  832. for (const ResourceID & file : files)
  833. {
  834. ui32 fileChecksum = filesystem->load(file)->calculateCRC32();
  835. modChecksum.process_bytes(reinterpret_cast<const void *>(&fileChecksum), sizeof(fileChecksum));
  836. }
  837. return modChecksum.checksum();
  838. }
  839. void CModHandler::loadModFilesystems()
  840. {
  841. CGeneralTextHandler::detectInstallParameters();
  842. activeMods = validateAndSortDependencies(activeMods);
  843. coreMod.updateChecksum(calculateModChecksum(CModHandler::scopeBuiltin(), CResourceHandler::get(CModHandler::scopeBuiltin())));
  844. for(std::string & modName : activeMods)
  845. {
  846. CModInfo & mod = allMods[modName];
  847. CResourceHandler::addFilesystem("data", modName, genModFilesystem(modName, mod.config));
  848. }
  849. }
  850. TModID CModHandler::findResourceOrigin(const ResourceID & name)
  851. {
  852. for(const auto & modID : boost::adaptors::reverse(activeMods))
  853. {
  854. if(CResourceHandler::get(modID)->existsResource(name))
  855. return modID;
  856. }
  857. if(CResourceHandler::get("core")->existsResource(name))
  858. return "core";
  859. if(CResourceHandler::get("mapEditor")->existsResource(name))
  860. return "core"; // Workaround for loading maps via map editor
  861. assert(0);
  862. return "";
  863. }
  864. std::string CModHandler::getModLanguage(const TModID& modId) const
  865. {
  866. if ( modId == "core")
  867. return VLC->generaltexth->getInstalledLanguage();
  868. return allMods.at(modId).baseLanguage;
  869. }
  870. std::set<TModID> CModHandler::getModDependencies(const TModID & modId, bool & isModFound) const
  871. {
  872. auto it = allMods.find(modId);
  873. isModFound = (it != allMods.end());
  874. if(isModFound)
  875. return it->second.dependencies;
  876. logMod->error("Mod not found: '%s'", modId);
  877. return {};
  878. }
  879. void CModHandler::initializeConfig()
  880. {
  881. VLC->settingsHandler->load(coreMod.config["settings"]);
  882. for(const TModID & modName : activeMods)
  883. {
  884. const auto & mod = allMods[modName];
  885. if (!mod.config["settings"].isNull())
  886. VLC->settingsHandler->load(mod.config["settings"]);
  887. }
  888. }
  889. bool CModHandler::validateTranslations(TModID modName) const
  890. {
  891. bool result = true;
  892. const auto & mod = allMods.at(modName);
  893. {
  894. auto fileList = mod.config["translations"].convertTo<std::vector<std::string> >();
  895. JsonNode json = JsonUtils::assembleFromFiles(fileList);
  896. result |= VLC->generaltexth->validateTranslation(mod.baseLanguage, modName, json);
  897. }
  898. for(const auto & language : Languages::getLanguageList())
  899. {
  900. if (!language.hasTranslation)
  901. continue;
  902. if (mod.config[language.identifier].isNull())
  903. continue;
  904. if (mod.config[language.identifier]["skipValidation"].Bool())
  905. continue;
  906. auto fileList = mod.config[language.identifier]["translations"].convertTo<std::vector<std::string> >();
  907. JsonNode json = JsonUtils::assembleFromFiles(fileList);
  908. result |= VLC->generaltexth->validateTranslation(language.identifier, modName, json);
  909. }
  910. return result;
  911. }
  912. void CModHandler::loadTranslation(const TModID & modName)
  913. {
  914. const auto & mod = allMods[modName];
  915. std::string preferredLanguage = VLC->generaltexth->getPreferredLanguage();
  916. std::string modBaseLanguage = allMods[modName].baseLanguage;
  917. auto baseTranslationList = mod.config["translations"].convertTo<std::vector<std::string> >();
  918. auto extraTranslationList = mod.config[preferredLanguage]["translations"].convertTo<std::vector<std::string> >();
  919. JsonNode baseTranslation = JsonUtils::assembleFromFiles(baseTranslationList);
  920. JsonNode extraTranslation = JsonUtils::assembleFromFiles(extraTranslationList);
  921. VLC->generaltexth->loadTranslationOverrides(modBaseLanguage, modName, baseTranslation);
  922. VLC->generaltexth->loadTranslationOverrides(preferredLanguage, modName, extraTranslation);
  923. }
  924. void CModHandler::load()
  925. {
  926. CStopWatch totalTime;
  927. CStopWatch timer;
  928. logMod->info("\tInitializing content handler: %d ms", timer.getDiff());
  929. content->init();
  930. for(const TModID & modName : activeMods)
  931. {
  932. logMod->trace("Generating checksum for %s", modName);
  933. allMods[modName].updateChecksum(calculateModChecksum(modName, CResourceHandler::get(modName)));
  934. }
  935. // first - load virtual builtin mod that contains all data
  936. // TODO? move all data into real mods? RoE, AB, SoD, WoG
  937. content->preloadData(coreMod);
  938. for(const TModID & modName : activeMods)
  939. content->preloadData(allMods[modName]);
  940. logMod->info("\tParsing mod data: %d ms", timer.getDiff());
  941. content->load(coreMod);
  942. for(const TModID & modName : activeMods)
  943. content->load(allMods[modName]);
  944. #if SCRIPTING_ENABLED
  945. VLC->scriptHandler->performRegistration(VLC);//todo: this should be done before any other handlers load
  946. #endif
  947. content->loadCustom();
  948. for(const TModID & modName : activeMods)
  949. loadTranslation(modName);
  950. for(const TModID & modName : activeMods)
  951. if (!validateTranslations(modName))
  952. allMods[modName].validation = CModInfo::FAILED;
  953. logMod->info("\tLoading mod data: %d ms", timer.getDiff());
  954. VLC->creh->loadCrExpMod();
  955. identifiers.finalize();
  956. logMod->info("\tResolving identifiers: %d ms", timer.getDiff());
  957. content->afterLoadFinalization();
  958. logMod->info("\tHandlers post-load finalization: %d ms ", timer.getDiff());
  959. logMod->info("\tAll game content loaded in %d ms", totalTime.getDiff());
  960. }
  961. void CModHandler::afterLoad(bool onlyEssential)
  962. {
  963. JsonNode modSettings;
  964. for (auto & modEntry : allMods)
  965. {
  966. std::string pointer = "/" + boost::algorithm::replace_all_copy(modEntry.first, ".", "/mods/");
  967. modSettings["activeMods"].resolvePointer(pointer) = modEntry.second.saveLocalData();
  968. }
  969. modSettings[CModHandler::scopeBuiltin()] = coreMod.saveLocalData();
  970. if(!onlyEssential)
  971. {
  972. std::fstream file(CResourceHandler::get()->getResourceName(ResourceID("config/modSettings.json"))->c_str(), std::ofstream::out | std::ofstream::trunc);
  973. file << modSettings.toJson();
  974. }
  975. }
  976. std::string CModHandler::normalizeIdentifier(const std::string & scope, const std::string & remoteScope, const std::string & identifier)
  977. {
  978. auto p = vstd::splitStringToPair(identifier, ':');
  979. if(p.first.empty())
  980. p.first = scope;
  981. if(p.first == remoteScope)
  982. p.first.clear();
  983. return p.first.empty() ? p.second : p.first + ":" + p.second;
  984. }
  985. void CModHandler::parseIdentifier(const std::string & fullIdentifier, std::string & scope, std::string & type, std::string & identifier)
  986. {
  987. auto p = vstd::splitStringToPair(fullIdentifier, ':');
  988. scope = p.first;
  989. auto p2 = vstd::splitStringToPair(p.second, '.');
  990. if(!p2.first.empty())
  991. {
  992. type = p2.first;
  993. identifier = p2.second;
  994. }
  995. else
  996. {
  997. type = p.second;
  998. identifier.clear();
  999. }
  1000. }
  1001. std::string CModHandler::makeFullIdentifier(const std::string & scope, const std::string & type, const std::string & identifier)
  1002. {
  1003. if(type.empty())
  1004. logGlobal->error("Full identifier (%s %s) requires type name", scope, identifier);
  1005. std::string actualScope = scope;
  1006. std::string actualName = identifier;
  1007. //ignore scope if identifier is scoped
  1008. auto scopeAndName = vstd::splitStringToPair(identifier, ':');
  1009. if(!scopeAndName.first.empty())
  1010. {
  1011. actualScope = scopeAndName.first;
  1012. actualName = scopeAndName.second;
  1013. }
  1014. if(actualScope.empty())
  1015. {
  1016. return actualName.empty() ? type : type + "." + actualName;
  1017. }
  1018. else
  1019. {
  1020. return actualName.empty() ? actualScope+ ":" + type : actualScope + ":" + type + "." + actualName;
  1021. }
  1022. }
  1023. VCMI_LIB_NAMESPACE_END