mcrpc.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  1. #include "mcrpc.h"
  2. #include <QCoreApplication>
  3. #include <QCryptographicHash>
  4. #include <QEventLoop>
  5. #include <QJsonArray>
  6. #include <QJsonDocument>
  7. #include <QJsonObject>
  8. #include <QNetworkReply>
  9. #include <QNetworkRequest>
  10. #include <QStandardPaths>
  11. #include <filesystem>
  12. #include "JlCompress.h"
  13. mcrpc::mcrpc(QObject *parent)
  14. : QObject{parent}, qout(stdout), qin(stdin)
  15. {
  16. // Set .minecraft directory
  17. dirDotMC = dirHome;
  18. // Consider using #if defined(Q_OS_LINUX)
  19. if (platform == "linux")
  20. {
  21. if (!dirDotMC.cd(".minecraft"))
  22. {
  23. qFatal(".minecraft does not exist!");
  24. }
  25. }
  26. else if (platform == "darwin")
  27. {
  28. if (!dirDotMC.cd("Library/Application Support/minecraft"))
  29. {
  30. qFatal("~/Library/Application Support/minecraft does not exist!");
  31. }
  32. }
  33. else if (platform == "winnt")
  34. {
  35. if (!dirDotMC.cd("AppData/.minecraft"))
  36. {
  37. qFatal(".minecraft does not exist!");
  38. }
  39. }
  40. // TODO(txtsd): Use mkdir instead of mkpath
  41. // Set cache directory
  42. QDir dirCache(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
  43. if (!dirCache.exists())
  44. {
  45. dirCache.mkpath(dirCache.absolutePath());
  46. }
  47. // Set jars directory
  48. if (!dirCache.exists("jars"))
  49. {
  50. dirCache.mkpath(dirCache.absolutePath() + "/jars");
  51. }
  52. dirJars = dirCache;
  53. dirJars.cd("jars");
  54. // Set resource packs directory
  55. if (!dirDotMC.exists("resourcepacks"))
  56. {
  57. dirDotMC.mkpath(dirDotMC.absolutePath() + "/resourcepacks");
  58. }
  59. dirRP = dirDotMC;
  60. dirRP.cd("resourcepacks");
  61. }
  62. void mcrpc::run()
  63. {
  64. while(true)
  65. {
  66. qout << "\n" << "[1] Compare a resource pack against a minecraft version" << "\n";
  67. qout << "[2] Compare a minecraft version against another minecraft version" << "\n";
  68. qout << "Choice: ";
  69. qout.flush();
  70. QString choiceLogic = qin.readLine();
  71. if (choiceLogic == '1')
  72. {
  73. rp = chooseRP();
  74. mcv1 = chooseMCV();
  75. compare(rp, mcv1, mcv2);
  76. break;
  77. }
  78. else if (choiceLogic == '2')
  79. {
  80. mcv1 = chooseMCV();
  81. mcv2 = chooseMCV();
  82. compare(rp, mcv1, mcv2);
  83. break;
  84. }
  85. else
  86. {
  87. qout << "\n" << "Invalid choice!" << "\n";
  88. qout.flush();
  89. }
  90. }
  91. // Quit after everything is done
  92. // QCoreApplication::quit();
  93. }
  94. QFileInfo mcrpc::chooseRP()
  95. {
  96. QFileInfoList listRP = dirRP.entryInfoList(QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot);
  97. QFileInfoList listRPChecked;
  98. for(const QFileInfo &item : listRP)
  99. {
  100. if (item.suffix() == "zip")
  101. {
  102. QStringList archiveContents = JlCompress::getFileList(item.absoluteFilePath());
  103. if (archiveContents.contains("pack.mcmeta"))
  104. {
  105. listRPChecked.append(item);
  106. }
  107. }
  108. else if (QFileInfo::exists(item.absoluteFilePath() + "/pack.mcmeta"))
  109. {
  110. listRPChecked.append(item);
  111. }
  112. }
  113. if (listRPChecked.isEmpty())
  114. {
  115. qout << "\n" << "No valid resource packs. Install one and try again.";
  116. qout.flush();
  117. QCoreApplication::quit();
  118. }
  119. while(true)
  120. {
  121. qout << "\n" << "Select the Resource Pack you want to compare:";
  122. qout.flush();
  123. int i = 1;
  124. for(const QFileInfo &item : listRPChecked)
  125. {
  126. qout << "\n " << "[" << i << "] " << item.fileName();
  127. qout.flush();
  128. i++;
  129. }
  130. qout << "\n" << "Resource Pack choice: ";
  131. qout.flush();
  132. QString choiceRPNum = qin.readLine();
  133. i = 1;
  134. for(const QFileInfo &item : listRPChecked)
  135. {
  136. if (QString::number(i) == choiceRPNum)
  137. {
  138. return item;
  139. }
  140. i++;
  141. }
  142. qout << "\n" << "Invalid choice!" << "\n";
  143. }
  144. }
  145. QJsonObject mcrpc::chooseMCV()
  146. {
  147. QEventLoop loop;
  148. QObject::connect(&qnam, &QNetworkAccessManager::finished,
  149. &loop, &QEventLoop::quit);
  150. QNetworkReply *reply = qnam.get(QNetworkRequest(linkVM));
  151. loop.exec();
  152. QJsonObject jsonObject;
  153. QUrl url = reply->url();
  154. if (reply->error() == QNetworkReply::NoError)
  155. {
  156. QByteArray response(reply->readAll());
  157. QJsonDocument jsonResponse = QJsonDocument::fromJson(response);
  158. jsonObject = jsonResponse.object();
  159. }
  160. reply->deleteLater();
  161. QString choiceStable;
  162. qout << "\n";
  163. qout.flush();
  164. while(true)
  165. {
  166. qout << "See non-stable versions? (y/N)" << "\n";
  167. qout.flush();
  168. choiceStable = qin.readLine();
  169. if (choiceStable == "y" || choiceStable == "Y" || choiceStable == "n" || choiceStable == "N")
  170. {
  171. break;
  172. }
  173. }
  174. qout << "\n" << "Select the Minecraft Version you want to compare against:" << "\n";
  175. qout.flush();
  176. QString choiceMCVNum;
  177. int counter = 0;
  178. int counterActual = 1;
  179. int listSize = 25;
  180. bool skipDisplay = false;
  181. while(true)
  182. {
  183. bool ok = false;
  184. for(QJsonValue entry : jsonObject["versions"].toArray())
  185. {
  186. counter++;
  187. if (counter >= jsonObject["versions"].toArray().size())
  188. {
  189. skipDisplay = true;
  190. }
  191. if (choiceStable == "n" || choiceStable == "N")
  192. {
  193. if (entry.toObject()["type"].toString() != "release")
  194. {
  195. continue;
  196. }
  197. }
  198. if (!skipDisplay)
  199. {
  200. qout << " [" << counterActual << "] " << entry.toObject()["id"].toString() << "\n";
  201. qout.flush();
  202. }
  203. if (counterActual % listSize == 0)
  204. {
  205. qout << "(Hit Enter to show more versions) Minecraft Version choice: ";
  206. qout.flush();
  207. choiceMCVNum = qin.readLine();
  208. // bool ok;
  209. choiceMCVNum.toInt(&ok);
  210. if (ok)
  211. {
  212. break;
  213. }
  214. }
  215. counterActual++;
  216. }
  217. if (ok)
  218. {
  219. break;
  220. }
  221. }
  222. QJsonObject choiceMCV;
  223. counterActual = 1;
  224. for(QJsonValue entry : jsonObject["versions"].toArray())
  225. {
  226. if (choiceStable == "n" || choiceStable == "N")
  227. {
  228. if (entry.toObject()["type"].toString() != "release")
  229. {
  230. continue;
  231. }
  232. }
  233. if (QString::number(counterActual) == choiceMCVNum)
  234. {
  235. choiceMCV = entry.toObject();
  236. }
  237. counterActual++;
  238. }
  239. return choiceMCV;
  240. }
  241. void mcrpc::compare(const QFileInfo &rp, const QJsonObject &mcv1, const QJsonObject &mcv2)
  242. {
  243. if (rp.exists() && !mcv1.isEmpty() && mcv2.isEmpty())
  244. {
  245. getClient(mcv1);
  246. // setupCompareFolders(true);
  247. QStringList rpList;
  248. if (rp.isFile())
  249. {
  250. rpList = JlCompress::getFileList(rp.absoluteFilePath());
  251. }
  252. else
  253. {
  254. QDir selectRPDir = rp.dir();
  255. selectRPDir.cd(rp.fileName());
  256. // this is not recursive
  257. // rpList = selectRPDir.entryList(QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot);
  258. // actually get all files
  259. for (std::filesystem::recursive_directory_iterator i(selectRPDir.path().toStdString()), end; i != end; ++i)
  260. {
  261. QString entry = QString::fromStdString(i->path().string());
  262. entry.replace(selectRPDir.path() + QDir::separator(), "");
  263. rpList.append(entry);
  264. }
  265. }
  266. // qDebug() << rpList << "\n" << rpList.size();
  267. qDebug() << rpList.size();
  268. QString clientPathStr = dirJars.absoluteFilePath(mcv1["id"].toString() + ".jar");
  269. QStringList mcv1List = JlCompress::getFileList(clientPathStr);
  270. QStringList mcv1ListFiltered;
  271. for (const QString &item : mcv1List)
  272. {
  273. if (item.indexOf("assets") == 0)
  274. {
  275. mcv1ListFiltered.append(item);
  276. }
  277. }
  278. qDebug() << mcv1ListFiltered.size();
  279. // Now compare lists and list missing files
  280. QStringList results;
  281. for (const QString &item : mcv1ListFiltered)
  282. {
  283. if (!rpList.contains(item, Qt::CaseInsensitive))
  284. {
  285. results.append(item);
  286. }
  287. }
  288. // qDebug() << "\n" << "Missing Files: " << "\n" << results << "\n" << results.size();
  289. qDebug() << results.size();
  290. }
  291. else if (!rp.exists() && !mcv1.isEmpty() && !mcv2.isEmpty())
  292. {
  293. getClient(mcv1);
  294. getClient(mcv2);
  295. // TODO(txtsd): Add logic to always compare older against newer
  296. // setupCompareFolders();
  297. QString clientPathStr1 = dirJars.absoluteFilePath(mcv1["id"].toString() + ".jar");
  298. QStringList mcv1List = JlCompress::getFileList(clientPathStr1);
  299. QStringList mcv1ListFiltered;
  300. for (const QString &item : mcv1List)
  301. {
  302. if (item.indexOf("assets") == 0)
  303. {
  304. mcv1ListFiltered.append(item);
  305. }
  306. }
  307. qDebug() << mcv1ListFiltered.size();
  308. QString clientPathStr2 = dirJars.absoluteFilePath(mcv2["id"].toString() + ".jar");
  309. QStringList mcv2List = JlCompress::getFileList(clientPathStr2);
  310. QStringList mcv2ListFiltered;
  311. for (const QString &item : mcv2List)
  312. {
  313. if (item.indexOf("assets") == 0)
  314. {
  315. mcv2ListFiltered.append(item);
  316. }
  317. }
  318. qDebug() << mcv2ListFiltered.size();
  319. QStringList results;
  320. for (const QString &item : mcv2ListFiltered)
  321. {
  322. if (!mcv1ListFiltered.contains(item, Qt::CaseInsensitive))
  323. {
  324. results.append(item);
  325. }
  326. }
  327. // qDebug() << "\n" << "Missing Files: " << "\n" << results << "\n" << results.size();
  328. qDebug() << results.size();
  329. }
  330. }
  331. void mcrpc::getClient(const QJsonObject &mcv)
  332. {
  333. QEventLoop loop;
  334. QObject::connect(&qnam, &QNetworkAccessManager::finished, &loop, &QEventLoop::quit);
  335. QNetworkReply *reply = qnam.get(QNetworkRequest(mcv["url"].toString()));
  336. loop.exec();
  337. QJsonObject jsonObject;
  338. QUrl url = reply->url();
  339. qDebug() << "url" << url;
  340. if (reply->error() == QNetworkReply::NoError)
  341. {
  342. QByteArray response(reply->readAll());
  343. QJsonDocument jsonResponse = QJsonDocument::fromJson(response);
  344. jsonObject = jsonResponse.object();
  345. }
  346. else
  347. {
  348. qCritical() << "1 Failed to download" << mcv["url"].toString();
  349. }
  350. reply->deleteLater();
  351. // QJsonObject _temp1 = jsonObject["downloads"].toObject();
  352. // qDebug() << "_temp1" << _temp1;
  353. QString clientLink = jsonObject["downloads"].toObject()["client"].toObject()["url"].toString();
  354. QString clientSha = jsonObject["downloads"].toObject()["client"].toObject()["sha1"].toString();
  355. // Check if these turn up empty
  356. qDebug() << "clientLink" << clientLink;
  357. qDebug() << "clientSha" << clientSha;
  358. QString clientPathStr = dirJars.absoluteFilePath(mcv["id"].toString() + ".jar");
  359. QFile clientPath(clientPathStr);
  360. // qDebug() << clientPath;
  361. QCryptographicHash cryptoHash(QCryptographicHash::Sha1);
  362. QByteArray hashByteArray = "";
  363. clientPath.open(QIODevice::ReadOnly);
  364. if (clientPath.exists())
  365. {
  366. qDebug() << "Client" << mcv["id"].toString() << "exists";
  367. // check and set sha1
  368. while(!clientPath.atEnd())
  369. {
  370. cryptoHash.addData(clientPath.readLine());
  371. }
  372. clientPath.close();
  373. hashByteArray = cryptoHash.result();
  374. }
  375. qDebug() << hashByteArray.toHex();
  376. // if sha1 does not match
  377. // download client
  378. if (QString(hashByteArray.toHex()) != clientSha)
  379. {
  380. qDebug() << "Downloading" << mcv["id"].toString();
  381. QEventLoop loop2;
  382. QNetworkReply *reply2 = qnam.get(QNetworkRequest(clientLink));
  383. QObject::connect(&qnam, &QNetworkAccessManager::finished,
  384. &loop2, &QEventLoop::quit);
  385. QObject::connect(reply2, &QNetworkReply::readyRead,
  386. [this, &clientPath, &reply2](){
  387. clientPath.write(reply2->readAll());
  388. });
  389. clientPath.open(QIODevice::WriteOnly | QIODevice::Append);
  390. clientPath.resize(0);
  391. loop2.exec();
  392. clientPath.close();
  393. reply2->deleteLater();
  394. }
  395. // TODO(txtsd): Check checksum after downloading
  396. }