chroniclesextractor.cpp 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  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. #ifdef ENABLE_INNOEXTRACT
  15. #include "cli/extract.hpp"
  16. #include "setup/version.hpp"
  17. #endif
  18. ChroniclesExtractor::ChroniclesExtractor(QWidget *p, std::function<void(float percent)> cb) :
  19. parent(p), cb(cb)
  20. {
  21. }
  22. bool ChroniclesExtractor::handleTempDir(bool create)
  23. {
  24. if(create)
  25. {
  26. tempDir = QDir(pathToQString(VCMIDirs::get().userDataPath()));
  27. if(tempDir.cd("tmp"))
  28. {
  29. tempDir.removeRecursively(); // remove if already exists (e.g. previous run)
  30. tempDir.cdUp();
  31. }
  32. tempDir.mkdir("tmp");
  33. if(!tempDir.cd("tmp"))
  34. return false; // should not happen - but avoid deleting wrong folder in any case
  35. }
  36. else
  37. tempDir.removeRecursively();
  38. return true;
  39. }
  40. int ChroniclesExtractor::getChronicleNo(QFile & file)
  41. {
  42. if(!file.open(QIODevice::ReadOnly))
  43. {
  44. QMessageBox::critical(parent, tr("File cannot opened"), file.errorString());
  45. return 0;
  46. }
  47. QByteArray magic{"MZ"};
  48. QByteArray magicFile = file.read(magic.length());
  49. if(!magicFile.startsWith(magic))
  50. {
  51. QMessageBox::critical(parent, tr("Invalid file selected"), tr("You have to select an gog installer file!"));
  52. return 0;
  53. }
  54. QByteArray dataBegin = file.read(1'000'000);
  55. int chronicle = 0;
  56. for (const auto& kv : chronicles) {
  57. if(dataBegin.contains(kv.second))
  58. {
  59. chronicle = kv.first;
  60. break;
  61. }
  62. }
  63. if(!chronicle)
  64. {
  65. QMessageBox::critical(parent, tr("Invalid file selected"), tr("You have to select an chronicle installer file!"));
  66. return 0;
  67. }
  68. return chronicle;
  69. }
  70. bool ChroniclesExtractor::extractGogInstaller(QString file)
  71. {
  72. #ifndef ENABLE_INNOEXTRACT
  73. QMessageBox::critical(parent, tr("Innoextract functionality missing"), "VCMI was compiled without innoextract support, which is needed to extract chroncles!");
  74. return false;
  75. #else
  76. ::extract_options o;
  77. o.extract = true;
  78. // standard settings
  79. o.gog_galaxy = true;
  80. o.codepage = 0U;
  81. o.output_dir = tempDir.path().toStdString();
  82. o.extract_temp = true;
  83. o.extract_unknown = true;
  84. o.filenames.set_expand(true);
  85. o.preserve_file_times = true; // also correctly closes file -> without it: on Windows the files are not written completely
  86. QString errorText = "";
  87. try
  88. {
  89. process_file(file.toStdString(), o, [this](float progress) {
  90. float overallProgress = ((1.0 / float(fileCount)) * float(extractionFile)) + (progress / float(fileCount));
  91. if(cb)
  92. cb(overallProgress);
  93. });
  94. }
  95. catch(const std::ios_base::failure & e)
  96. {
  97. errorText = tr("Stream error while extracting files!\nerror reason: ");
  98. errorText += e.what();
  99. }
  100. catch(const format_error & e)
  101. {
  102. errorText = e.what();
  103. }
  104. catch(const std::runtime_error & e)
  105. {
  106. errorText = e.what();
  107. }
  108. catch(const setup::version_error &)
  109. {
  110. errorText = tr("Not a supported Inno Setup installer!");
  111. }
  112. if(!errorText.isEmpty())
  113. {
  114. QMessageBox::critical(parent, tr("Extracting error!"), errorText);
  115. return false;
  116. }
  117. return true;
  118. #endif
  119. }
  120. void ChroniclesExtractor::createBaseMod()
  121. {
  122. QDir dir(pathToQString(VCMIDirs::get().userDataPath() / "Mods"));
  123. dir.mkdir("chronicles");
  124. dir.cd("chronicles");
  125. dir.mkdir("Mods");
  126. QJsonObject mod
  127. {
  128. { "modType", "Expansion" },
  129. { "name", "Heroes Chronicles" },
  130. { "description", "" },
  131. { "author", "3DO" },
  132. { "version", "1.0" },
  133. };
  134. QFile jsonFile(dir.filePath("mod.json"));
  135. jsonFile.open(QFile::WriteOnly);
  136. jsonFile.write(QJsonDocument(mod).toJson());
  137. }
  138. void ChroniclesExtractor::createChronicleMod(int no)
  139. {
  140. QDir dir(pathToQString(VCMIDirs::get().userDataPath() / "Mods" / "chronicles" / "Mods" / ("chronicles_" + std::to_string(no))));
  141. dir.removeRecursively();
  142. dir.mkpath(".");
  143. QJsonObject mod
  144. {
  145. { "modType", "Expansion" },
  146. { "name", "Heroes Chronicles - " + QString::number(no) },
  147. { "description", "" },
  148. { "author", "3DO" },
  149. { "version", "1.0" },
  150. };
  151. QFile jsonFile(dir.filePath("mod.json"));
  152. jsonFile.open(QFile::WriteOnly);
  153. jsonFile.write(QJsonDocument(mod).toJson());
  154. dir.cd("content");
  155. extractFiles(no);
  156. }
  157. void ChroniclesExtractor::extractFiles(int no)
  158. {
  159. QByteArray tmpChronicles = chronicles.at(no);
  160. tmpChronicles.replace('\0', "");
  161. QDir tmpDir = tempDir.filePath(tempDir.entryList({"app"}, QDir::Filter::Dirs).front());
  162. tmpDir.setPath(tmpDir.filePath(tmpDir.entryList({QString(tmpChronicles)}, QDir::Filter::Dirs).front()));
  163. tmpDir.setPath(tmpDir.filePath(tmpDir.entryList({"data"}, QDir::Filter::Dirs).front()));
  164. auto basePath = VCMIDirs::get().userDataPath() / "Mods" / "chronicles" / "Mods" / ("chronicles_" + std::to_string(no)) / "content";
  165. QDir outDirData(pathToQString(basePath / "Data"));
  166. QDir outDirSprites(pathToQString(basePath / "Sprites"));
  167. QDir outDirVideo(pathToQString(basePath / "Video"));
  168. QDir outDirSounds(pathToQString(basePath / "Sounds"));
  169. QDir outDirMaps(pathToQString(basePath / "Maps"));
  170. auto extract = [](QDir scrDir, QDir dest, QString file, std::vector<std::string> files = {}){
  171. CArchiveLoader archive("", scrDir.filePath(scrDir.entryList({file}).front()).toStdString(), false);
  172. for(auto & entry : archive.getEntries())
  173. if(files.empty())
  174. archive.extractToFolder(dest.absolutePath().toStdString(), "", entry.second, true);
  175. else
  176. {
  177. for(auto & item : files)
  178. if(!boost::algorithm::to_lower_copy(entry.second.name).find(boost::algorithm::to_lower_copy(item)))
  179. archive.extractToFolder(dest.absolutePath().toStdString(), "", entry.second, true);
  180. }
  181. };
  182. auto rename = [no](QDir dest){
  183. dest.refresh();
  184. for(auto & entry : dest.entryList())
  185. {
  186. if(entry.toUpper().startsWith("HPS") || entry.toUpper().startsWith("HPL"))
  187. dest.rename(entry, "Hc_" + entry);
  188. if(!entry.startsWith("Hc" + QString::number(no) + "_"))
  189. dest.rename(entry, "Hc" + QString::number(no) + "_" + entry);
  190. }
  191. };
  192. extract(tmpDir, outDirData, "xBitmap.lod");
  193. extract(tmpDir, outDirData, "xlBitmap.lod");
  194. extract(tmpDir, outDirSprites, "xSprite.lod");
  195. extract(tmpDir, outDirSprites, "xlSprite.lod");
  196. extract(tmpDir, outDirVideo, "xVideo.vid");
  197. extract(tmpDir, outDirSounds, "xSound.snd");
  198. tmpDir.cdUp();
  199. if(tmpDir.entryList({"maps"}, QDir::Filter::Dirs).size())
  200. {
  201. QDir tmpDirMaps = tmpDir.filePath(tmpDir.entryList({"maps"}, QDir::Filter::Dirs).front());
  202. for(auto & entry : tmpDirMaps.entryList())
  203. QFile(tmpDirMaps.filePath(entry)).copy(outDirData.filePath(entry));
  204. }
  205. tmpDir.cdUp();
  206. QDir tmpDirData = tmpDir.filePath(tmpDir.entryList({"data"}, QDir::Filter::Dirs).front());
  207. extract(tmpDirData, outDirData, "bitmap.lod", std::vector<std::string>{"HPS137", "HPS138", "HPL139", "HPS140", "HPS141", "HPS142", "HPL137", "HPL138", "HPL139", "HPL140", "HPL141", "HPL142"});
  208. extract(tmpDirData, outDirData, "lbitmap.lod", std::vector<std::string>{"INTRORIM"});
  209. rename(outDirData);
  210. rename(outDirSprites);
  211. rename(outDirVideo);
  212. rename(outDirSounds);
  213. if(!outDirMaps.exists())
  214. outDirMaps.mkpath(".");
  215. QString campaignFileName = "Hc" + QString::number(no) + "_Main.h3c";
  216. QFile(outDirData.filePath(outDirData.entryList({campaignFileName}).front())).copy(outDirMaps.filePath(campaignFileName));
  217. }
  218. void ChroniclesExtractor::installChronicles(QStringList exe)
  219. {
  220. extractionFile = -1;
  221. fileCount = exe.size();
  222. for(QString f : exe)
  223. {
  224. extractionFile++;
  225. QFile file(f);
  226. int chronicleNo = getChronicleNo(file);
  227. if(!chronicleNo)
  228. continue;
  229. if(!handleTempDir(true))
  230. continue;
  231. if(!extractGogInstaller(f))
  232. continue;
  233. createBaseMod();
  234. createChronicleMod(chronicleNo);
  235. handleTempDir(false);
  236. }
  237. }