cmodmanager.cpp 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. #include "StdInc.h"
  2. #include "cmodmanager.h"
  3. #include "../../lib/VCMIDirs.h"
  4. #include "../../lib/filesystem/Filesystem.h"
  5. #include "../../lib/filesystem/CZipLoader.h"
  6. #include "../../lib/CModHandler.h"
  7. #include "../jsonutils.h"
  8. #include "../launcherdirs.h"
  9. static QString detectModArchive(QString path, QString modName)
  10. {
  11. auto files = ZipArchive::listFiles(path.toUtf8().data());
  12. QString modDirName;
  13. for (auto file : files)
  14. {
  15. QString filename = QString::fromUtf8(file.c_str());
  16. if (filename.toLower().startsWith(modName))
  17. {
  18. // archive must contain mod.json file
  19. if (filename.toLower() == modName + "/mod.json")
  20. modDirName = filename.section('/', 0, 0);
  21. }
  22. else // all files must be in <modname> directory
  23. return "";
  24. }
  25. return modDirName;
  26. }
  27. CModManager::CModManager(CModList * modList):
  28. modList(modList)
  29. {
  30. loadMods();
  31. loadModSettings();
  32. }
  33. QString CModManager::settingsPath()
  34. {
  35. return QString::fromUtf8(VCMIDirs::get().userConfigPath().c_str()) + "/modSettings.json";
  36. }
  37. void CModManager::loadModSettings()
  38. {
  39. modSettings = JsonUtils::JsonFromFile(settingsPath()).toMap();
  40. modList->setModSettings(modSettings["activeMods"]);
  41. }
  42. void CModManager::resetRepositories()
  43. {
  44. modList->resetRepositories();
  45. }
  46. void CModManager::loadRepository(QString file)
  47. {
  48. modList->addRepository(JsonUtils::JsonFromFile(file).toMap());
  49. }
  50. void CModManager::loadMods()
  51. {
  52. CModHandler handler;
  53. handler.loadMods();
  54. auto installedMods = handler.getAllMods();
  55. for (auto modname : installedMods)
  56. {
  57. ResourceID resID(CModInfo::getModFile(modname));
  58. if (CResourceHandler::get()->existsResource(resID))
  59. {
  60. std::string name = *CResourceHandler::get()->getResourceName(resID);
  61. auto mod = JsonUtils::JsonFromFile(QString::fromUtf8(name.c_str()));
  62. localMods.insert(QString::fromUtf8(modname.c_str()).toLower(), mod);
  63. }
  64. }
  65. modList->setLocalModList(localMods);
  66. }
  67. bool CModManager::addError(QString modname, QString message)
  68. {
  69. recentErrors.push_back(QString("%1: %2").arg(modname).arg(message));
  70. return false;
  71. }
  72. QStringList CModManager::getErrors()
  73. {
  74. QStringList ret = recentErrors;
  75. recentErrors.clear();
  76. return ret;
  77. }
  78. bool CModManager::installMod(QString modname, QString archivePath)
  79. {
  80. return canInstallMod(modname) && doInstallMod(modname, archivePath);
  81. }
  82. bool CModManager::uninstallMod(QString modname)
  83. {
  84. return canUninstallMod(modname) && doUninstallMod(modname);
  85. }
  86. bool CModManager::enableMod(QString modname)
  87. {
  88. return canEnableMod(modname) && doEnableMod(modname, true);
  89. }
  90. bool CModManager::disableMod(QString modname)
  91. {
  92. return canDisableMod(modname) && doEnableMod(modname, false);
  93. }
  94. bool CModManager::canInstallMod(QString modname)
  95. {
  96. auto mod = modList->getMod(modname);
  97. if (mod.getName().contains('.'))
  98. return addError(modname, "Can not install submod");
  99. if (mod.isInstalled())
  100. return addError(modname, "Mod is already installed");
  101. if (!mod.isAvailable())
  102. return addError(modname, "Mod is not available");
  103. return true;
  104. }
  105. bool CModManager::canUninstallMod(QString modname)
  106. {
  107. auto mod = modList->getMod(modname);
  108. if (mod.getName().contains('.'))
  109. return addError(modname, "Can not uninstall submod");
  110. if (!mod.isInstalled())
  111. return addError(modname, "Mod is not installed");
  112. if (mod.isEnabled())
  113. return addError(modname, "Mod must be disabled first");
  114. return true;
  115. }
  116. bool CModManager::canEnableMod(QString modname)
  117. {
  118. auto mod = modList->getMod(modname);
  119. if (mod.isEnabled())
  120. return addError(modname, "Mod is already enabled");
  121. if (!mod.isInstalled())
  122. return addError(modname, "Mod must be installed first");
  123. for (auto modEntry : mod.getValue("depends").toStringList())
  124. {
  125. if (!modList->hasMod(modEntry)) // required mod is not available
  126. return addError(modname, QString("Required mod %1 is missing").arg(modEntry));
  127. if (!modList->getMod(modEntry).isEnabled())
  128. return addError(modname, QString("Required mod %1 is not enabled").arg(modEntry));
  129. }
  130. for (QString modEntry : modList->getModList())
  131. {
  132. auto mod = modList->getMod(modEntry);
  133. // "reverse conflict" - enabled mod has this one as conflict
  134. if (mod.isEnabled() && mod.getValue("conflicts").toStringList().contains(modname))
  135. return addError(modname, QString("This mod conflicts with %1").arg(modEntry));
  136. }
  137. for (auto modEntry : mod.getValue("conflicts").toStringList())
  138. {
  139. if (modList->hasMod(modEntry) &&
  140. modList->getMod(modEntry).isEnabled()) // conflicting mod installed and enabled
  141. return addError(modname, QString("This mod conflicts with %1").arg(modEntry));
  142. }
  143. return true;
  144. }
  145. bool CModManager::canDisableMod(QString modname)
  146. {
  147. auto mod = modList->getMod(modname);
  148. if (mod.isDisabled())
  149. return addError(modname, "Mod is already disabled");
  150. if (!mod.isInstalled())
  151. return addError(modname, "Mod must be installed first");
  152. for (QString modEntry : modList->getModList())
  153. {
  154. auto current = modList->getMod(modEntry);
  155. if (current.getValue("depends").toStringList().contains(modname) &&
  156. current.isEnabled())
  157. return addError(modname, QString("This mod is needed to run %1").arg(modEntry));
  158. }
  159. return true;
  160. }
  161. static QVariant writeValue(QString path, QVariantMap input, QVariant value)
  162. {
  163. if (path.size() > 1)
  164. {
  165. QString entryName = path.section('/', 0, 1);
  166. QString remainder = "/" + path.section('/', 2, -1);
  167. entryName.remove(0, 1);
  168. input.insert(entryName, writeValue(remainder, input.value(entryName).toMap(), value));
  169. return input;
  170. }
  171. else
  172. {
  173. return value;
  174. }
  175. }
  176. bool CModManager::doEnableMod(QString mod, bool on)
  177. {
  178. QString path = mod;
  179. path = "/activeMods/" + path.replace(".", "/mods/") + "/active";
  180. modSettings = writeValue(path, modSettings, QVariant(on)).toMap();
  181. modList->setModSettings(modSettings["activeMods"]);
  182. modList->modChanged(mod);
  183. JsonUtils::JsonToFile(settingsPath(), modSettings);
  184. return true;
  185. }
  186. bool CModManager::doInstallMod(QString modname, QString archivePath)
  187. {
  188. QString destDir = CLauncherDirs::get().modsPath() + "/";
  189. if (!QFile(archivePath).exists())
  190. return addError(modname, "Mod archive is missing");
  191. if (localMods.contains(modname))
  192. return addError(modname, "Mod with such name is already installed");
  193. QString modDirName = detectModArchive(archivePath, modname);
  194. if (!modDirName.size())
  195. return addError(modname, "Mod archive is invalid or corrupted");
  196. if (!ZipArchive::extract(archivePath.toUtf8().data(), destDir.toUtf8().data()))
  197. {
  198. QDir(destDir + modDirName).removeRecursively();
  199. return addError(modname, "Failed to extract mod data");
  200. }
  201. QVariantMap json = JsonUtils::JsonFromFile(destDir + modDirName + "/mod.json").toMap();
  202. localMods.insert(modname, json);
  203. modList->setLocalModList(localMods);
  204. modList->modChanged(modname);
  205. return true;
  206. }
  207. bool CModManager::doUninstallMod(QString modname)
  208. {
  209. ResourceID resID(std::string("Mods/") + modname.toUtf8().data(), EResType::DIRECTORY);
  210. // Get location of the mod, in case-insensitive way
  211. QString modDir = QString::fromUtf8(CResourceHandler::get()->getResourceName(resID)->c_str());
  212. if (!QDir(modDir).exists())
  213. return addError(modname, "Data with this mod was not found");
  214. if (!localMods.contains(modname))
  215. return addError(modname, "Data with this mod was not found");
  216. if (!QDir(modDir).removeRecursively())
  217. return addError(modname, "Failed to delete mod data");
  218. localMods.remove(modname);
  219. modList->setLocalModList(localMods);
  220. modList->modChanged(modname);
  221. return true;
  222. }