chroniclesextractor.cpp 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. /*
  2. * chroniclesextractor.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 "chroniclesextractor.h"
  12. #include "../../lib/VCMIDirs.h"
  13. #include "../../lib/filesystem/CArchiveLoader.h"
  14. #include "../innoextract.h"
  15. ChroniclesExtractor::ChroniclesExtractor(QWidget *p, std::function<void(float percent)> cb) :
  16. parent(p), cb(cb)
  17. {
  18. }
  19. bool ChroniclesExtractor::createTempDir()
  20. {
  21. tempDir = QDir(pathToQString(VCMIDirs::get().userDataPath()));
  22. if(tempDir.cd("tmp"))
  23. {
  24. tempDir.removeRecursively(); // remove if already exists (e.g. previous run)
  25. tempDir.cdUp();
  26. }
  27. tempDir.mkdir("tmp");
  28. if(!tempDir.cd("tmp"))
  29. return false; // should not happen - but avoid deleting wrong folder in any case
  30. return true;
  31. }
  32. void ChroniclesExtractor::removeTempDir()
  33. {
  34. tempDir.removeRecursively();
  35. }
  36. int ChroniclesExtractor::getChronicleNo(QFile & file)
  37. {
  38. if(!file.open(QIODevice::ReadOnly))
  39. {
  40. QMessageBox::critical(parent, tr("File cannot opened"), file.errorString());
  41. return 0;
  42. }
  43. QByteArray magic{"MZ"};
  44. QByteArray magicFile = file.read(magic.length());
  45. if(!magicFile.startsWith(magic))
  46. {
  47. QMessageBox::critical(parent, tr("Invalid file selected"), tr("You have to select an gog installer file!"));
  48. return 0;
  49. }
  50. QByteArray dataBegin = file.read(1'000'000);
  51. int chronicle = 0;
  52. for (const auto& kv : chronicles) {
  53. if(dataBegin.contains(kv.second))
  54. {
  55. chronicle = kv.first;
  56. break;
  57. }
  58. }
  59. if(!chronicle)
  60. {
  61. QMessageBox::critical(parent, tr("Invalid file selected"), tr("You have to select an chronicle installer file!"));
  62. return 0;
  63. }
  64. return chronicle;
  65. }
  66. bool ChroniclesExtractor::extractGogInstaller(QString file)
  67. {
  68. QString errorText = Innoextract::extract(file, tempDir.path(), [this](float progress) {
  69. float overallProgress = ((1.0 / static_cast<float>(fileCount)) * static_cast<float>(extractionFile)) + (progress / static_cast<float>(fileCount));
  70. if(cb)
  71. cb(overallProgress);
  72. });
  73. if(!errorText.isEmpty())
  74. {
  75. QString hashError = Innoextract::getHashError(file, {}, {}, {});
  76. QMessageBox::critical(parent, tr("Extracting error!"), errorText);
  77. if(!hashError.isEmpty())
  78. QMessageBox::critical(parent, tr("Hash error!"), hashError, QMessageBox::Ok, QMessageBox::Ok);
  79. return false;
  80. }
  81. return true;
  82. }
  83. void ChroniclesExtractor::createBaseMod() const
  84. {
  85. QDir dir(pathToQString(VCMIDirs::get().userDataPath() / "Mods"));
  86. dir.mkdir("chronicles");
  87. dir.cd("chronicles");
  88. dir.mkdir("Mods");
  89. QJsonObject mod
  90. {
  91. { "modType", "Expansion" },
  92. { "name", tr("Heroes Chronicles") },
  93. { "description", tr("Heroes Chronicles") },
  94. { "author", "3DO" },
  95. { "version", "1.0" },
  96. { "contact", "vcmi.eu" },
  97. { "heroes", QJsonArray({"config/portraitsChronicles.json"}) },
  98. { "settings", QJsonObject({{"mapFormat", QJsonObject({{"chronicles", QJsonObject({{
  99. {"supported", true},
  100. {"portraits", QJsonObject({
  101. {"portraitTarnumBarbarian", 163},
  102. {"portraitTarnumKnight", 164},
  103. {"portraitTarnumWizard", 165},
  104. {"portraitTarnumRanger", 166},
  105. {"portraitTarnumOverlord", 167},
  106. {"portraitTarnumBeastmaster", 168},
  107. })},
  108. }})}})}})},
  109. };
  110. QFile jsonFile(dir.filePath("mod.json"));
  111. jsonFile.open(QFile::WriteOnly);
  112. jsonFile.write(QJsonDocument(mod).toJson());
  113. for(auto & dataPath : VCMIDirs::get().dataPaths())
  114. {
  115. auto file = dataPath / "config" / "heroes" / "portraitsChronicles.json";
  116. auto destFolder = VCMIDirs::get().userDataPath() / "Mods" / "chronicles" / "content" / "config";
  117. if(boost::filesystem::exists(file))
  118. {
  119. boost::filesystem::create_directories(destFolder);
  120. #if BOOST_VERSION >= 107400
  121. boost::filesystem::copy_file(file, destFolder / "portraitsChronicles.json", boost::filesystem::copy_options::overwrite_existing);
  122. #else
  123. boost::filesystem::copy_file(file, destFolder / "portraitsChronicles.json", boost::filesystem::copy_option::overwrite_if_exists);
  124. #endif
  125. }
  126. }
  127. }
  128. void ChroniclesExtractor::createChronicleMod(int no)
  129. {
  130. QDir dir(pathToQString(VCMIDirs::get().userDataPath() / "Mods" / "chronicles" / "Mods" / ("chronicles_" + std::to_string(no))));
  131. dir.removeRecursively();
  132. dir.mkpath(".");
  133. QByteArray tmpChronicles = chronicles.at(no);
  134. tmpChronicles.replace('\0', "");
  135. QJsonObject mod
  136. {
  137. { "modType", "Expansion" },
  138. { "name", QString::number(no) + " - " + QString(tmpChronicles) },
  139. { "description", tr("Heroes Chronicles") + " - " + QString::number(no) + " - " + QString(tmpChronicles) },
  140. { "author", "3DO" },
  141. { "version", "1.0" },
  142. { "contact", "vcmi.eu" },
  143. };
  144. QFile jsonFile(dir.filePath("mod.json"));
  145. jsonFile.open(QFile::WriteOnly);
  146. jsonFile.write(QJsonDocument(mod).toJson());
  147. dir.cd("content");
  148. extractFiles(no);
  149. }
  150. void ChroniclesExtractor::extractFiles(int no) const
  151. {
  152. QByteArray tmpChronicles = chronicles.at(no);
  153. tmpChronicles.replace('\0', "");
  154. std::string chroniclesDir = "chronicles_" + std::to_string(no);
  155. QDir tmpDir = tempDir.filePath(tempDir.entryList({"app"}, QDir::Filter::Dirs).front());
  156. tmpDir.setPath(tmpDir.filePath(tmpDir.entryList({QString(tmpChronicles)}, QDir::Filter::Dirs).front()));
  157. tmpDir.setPath(tmpDir.filePath(tmpDir.entryList({"data"}, QDir::Filter::Dirs).front()));
  158. auto basePath = VCMIDirs::get().userDataPath() / "Mods" / "chronicles" / "Mods" / chroniclesDir / "content";
  159. QDir outDirDataPortraits(pathToQString(VCMIDirs::get().userDataPath() / "Mods" / "chronicles" / "content" / "Data"));
  160. QDir outDirData(pathToQString(basePath / "Data" / chroniclesDir));
  161. QDir outDirSprites(pathToQString(basePath / "Sprites" / chroniclesDir));
  162. QDir outDirVideo(pathToQString(basePath / "Video" / chroniclesDir));
  163. QDir outDirSounds(pathToQString(basePath / "Sounds" / chroniclesDir));
  164. QDir outDirMaps(pathToQString(basePath / "Maps" / "Chronicles"));
  165. auto extract = [](QDir scrDir, QDir dest, QString file, std::vector<std::string> files = {}){
  166. CArchiveLoader archive("", scrDir.filePath(scrDir.entryList({file}).front()).toStdString(), false);
  167. for(auto & entry : archive.getEntries())
  168. if(files.empty())
  169. archive.extractToFolder(dest.absolutePath().toStdString(), "", entry.second, true);
  170. else
  171. {
  172. for(const auto & item : files)
  173. if(boost::algorithm::to_lower_copy(entry.second.name).find(boost::algorithm::to_lower_copy(item)) != std::string::npos)
  174. archive.extractToFolder(dest.absolutePath().toStdString(), "", entry.second, true);
  175. }
  176. };
  177. extract(tmpDir, outDirData, "xBitmap.lod");
  178. extract(tmpDir, outDirData, "xlBitmap.lod");
  179. extract(tmpDir, outDirSprites, "xSprite.lod");
  180. extract(tmpDir, outDirSprites, "xlSprite.lod");
  181. extract(tmpDir, outDirVideo, "xVideo.vid");
  182. extract(tmpDir, outDirSounds, "xSound.snd");
  183. tmpDir.cdUp();
  184. if(tmpDir.entryList({"maps"}, QDir::Filter::Dirs).size()) // special case for "The World Tree": the map is in the "Maps" folder instead of inside the lod
  185. {
  186. QDir tmpDirMaps = tmpDir.filePath(tmpDir.entryList({"maps"}, QDir::Filter::Dirs).front());
  187. for(const auto & entry : tmpDirMaps.entryList())
  188. QFile(tmpDirMaps.filePath(entry)).copy(outDirData.filePath(entry));
  189. }
  190. tmpDir.cdUp();
  191. QDir tmpDirData = tmpDir.filePath(tmpDir.entryList({"data"}, QDir::Filter::Dirs).front());
  192. auto tarnumPortraits = std::vector<std::string>{"HPS137", "HPS138", "HPS139", "HPS140", "HPS141", "HPS142", "HPL137", "HPL138", "HPL139", "HPL140", "HPL141", "HPL142"};
  193. extract(tmpDirData, outDirDataPortraits, "bitmap.lod", tarnumPortraits);
  194. extract(tmpDirData, outDirData, "lbitmap.lod", std::vector<std::string>{"INTRORIM"});
  195. if(!outDirMaps.exists())
  196. outDirMaps.mkpath(".");
  197. QString campaignFileName = "Hc" + QString::number(no) + "_Main.h3c";
  198. QFile(outDirData.filePath(outDirData.entryList({"Main.h3c"}).front())).copy(outDirMaps.filePath(campaignFileName));
  199. }
  200. void ChroniclesExtractor::installChronicles(QStringList exe)
  201. {
  202. extractionFile = -1;
  203. fileCount = exe.size();
  204. for(QString f : exe)
  205. {
  206. extractionFile++;
  207. QFile file(f);
  208. int chronicleNo = getChronicleNo(file);
  209. if(!chronicleNo)
  210. continue;
  211. if(!createTempDir())
  212. continue;
  213. if(!extractGogInstaller(f))
  214. continue;
  215. createBaseMod();
  216. createChronicleMod(chronicleNo);
  217. removeTempDir();
  218. }
  219. }