DownloadQueue.cpp 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891
  1. #include <iostream>
  2. #include <string>
  3. #include <cpp3ds/System/I18n.hpp>
  4. #include <TweenEngine/Tween.h>
  5. #include <cpp3ds/System/Sleep.hpp>
  6. #include <cpp3ds/System/Err.hpp>
  7. #include <cpp3ds/System/FileSystem.hpp>
  8. #include <fstream>
  9. #include <rapidjson/ostreamwrapper.h>
  10. #include <rapidjson/writer.h>
  11. #include <cpp3ds/System/FileInputStream.hpp>
  12. #include <cpp3ds/System/Lock.hpp>
  13. #include <cmath>
  14. #include <ctime>
  15. #include "DownloadQueue.hpp"
  16. #include "Notification.hpp"
  17. #include "AppList.hpp"
  18. #include "TitleKeys.hpp"
  19. #include "InstalledList.hpp"
  20. #include "Config.hpp"
  21. #include "Util.hpp"
  22. #include "Theme.hpp"
  23. #include "States/SleepState.hpp"
  24. #include <inttypes.h>
  25. #ifndef EMULATION
  26. #include <3ds.h>
  27. #include "MCU/Mcu.hpp"
  28. #endif
  29. namespace FreeShop {
  30. DownloadItem::DownloadItem(std::shared_ptr<AppItem> appItem, Download *download, Installer *installer)
  31. : appItem(appItem)
  32. , download(download)
  33. , installer(installer)
  34. {
  35. }
  36. DownloadItem::~DownloadItem()
  37. {
  38. if (installer)
  39. delete installer;
  40. if (download)
  41. delete download;
  42. }
  43. DownloadQueue::DownloadQueue()
  44. : m_threadRefresh(&DownloadQueue::refresh, this)
  45. , m_refreshEnd(false)
  46. , m_isSleepInstalling(false)
  47. , m_size(320.f, 0.f)
  48. {
  49. if (Theme::isSoundChimeThemed)
  50. m_soundBufferFinish.loadFromFile(FREESHOP_DIR "/theme/sounds/chime.ogg");
  51. else
  52. m_soundBufferFinish.loadFromFile("sounds/chime.ogg");
  53. m_soundFinish.setBuffer(m_soundBufferFinish);
  54. load();
  55. m_threadRefresh.launch();
  56. }
  57. DownloadQueue::~DownloadQueue()
  58. {
  59. m_refreshEnd = true;
  60. cpp3ds::Lock lock(m_mutexRefresh);
  61. m_downloads.clear();
  62. }
  63. void DownloadQueue::addDownload(std::shared_ptr<AppItem> app, cpp3ds::Uint64 titleId, DownloadCompleteCallback callback, int contentIndex, float progress)
  64. {
  65. cpp3ds::Lock lock(m_mutexRefresh);
  66. cpp3ds::Uint32 type = titleId >> 32;
  67. if (cpp3ds::Keyboard::isKeyDown(cpp3ds::Keyboard::Select)) {
  68. app->queueForSleepInstall(false, type == TitleKeys::Demo);
  69. return;
  70. }
  71. if (titleId == 0)
  72. {
  73. if (app->isInstalled()) // Don't allow reinstalling without deleting
  74. return;
  75. titleId = app->getTitleId();
  76. }
  77. #ifndef EMULATION
  78. //Check if the title is not a system app (because it pass the verification for some reason)
  79. size_t titleType = app->getTitleId() >> 32;
  80. if (titleType != TitleKeys::SystemApplet && titleType != TitleKeys::SystemApplication) {
  81. bool isRegistered;
  82. NIMS_IsTaskRegistered(app->getTitleId(), &isRegistered);
  83. if (isRegistered || app->isSleepBusy()) {
  84. Notification::spawn(_("Registered for sleep download: \n%s", app->getTitle().toAnsiString().c_str()));
  85. return;
  86. }
  87. }
  88. #endif
  89. cpp3ds::String title = app->getTitle();
  90. if (type != TitleKeys::Game)
  91. {
  92. if (type == TitleKeys::Update)
  93. title = _("[Update] %s", title.toAnsiString().c_str());
  94. else if (type == TitleKeys::Demo)
  95. title = _("[Demo] %s", title.toAnsiString().c_str());
  96. else if (type == TitleKeys::DLC)
  97. title = _("[DLC] %s", title.toAnsiString().c_str());
  98. }
  99. std::string url = _("http://ccs.cdn.c.shop.nintendowifi.net/ccs/download/%016llX/tmd", titleId);
  100. Download* download = new Download(url);
  101. download->fillFromAppItem(app);
  102. download->m_textTitle.setString(title);
  103. download->setPosition(0.f, 240.f);
  104. download->setRetryCount(INT_MAX);
  105. download->setTimeout(cpp3ds::seconds(Config::get(Config::DownloadTimeout).GetFloat()));
  106. download->setBufferSize(1024 * Config::get(Config::DownloadBufferSize).GetUint());
  107. download->setSendTopCallback([this](Download *d){
  108. sendTop(d);
  109. });
  110. std::vector<char> buf; // Store fetched files (only used for TMD atm)
  111. cpp3ds::Clock clock;
  112. float count = 0;
  113. int fileIndex = 0; // Keep track of the multiple files, first is usually the TMD with rest being contents
  114. size_t fileSize = 0; // Size of each file (as obtain by http Content-Length)
  115. Installer *installer = new Installer(titleId, contentIndex);
  116. cpp3ds::Uint64 titleFileSize = 0;
  117. cpp3ds::Uint64 totalProcessed;
  118. // Is resuming from saved queue
  119. bool isResuming = contentIndex >= 0;
  120. // TMD values
  121. cpp3ds::Uint16 contentCount;
  122. cpp3ds::Uint16 titleVersion;
  123. std::vector<cpp3ds::Uint16> contentIndices;
  124. download->setDataCallback([=](const void* data, size_t len, size_t processed, const cpp3ds::Http::Response& response) mutable
  125. {
  126. // This condition should be true when download first starts.
  127. if (len == processed) {
  128. std::string length = response.getField("Content-Length");
  129. if (!length.empty())
  130. fileSize = strtoul(length.c_str(), 0, 10);
  131. }
  132. const char *bufdata = reinterpret_cast<const char*>(data);
  133. if (fileIndex == 0)
  134. {
  135. buf.insert(buf.end(), bufdata, bufdata + len);
  136. if (processed == fileSize && fileSize != 0)
  137. {
  138. static int dataOffsets[6] = {0x240, 0x140, 0x80, 0x240, 0x140, 0x80};
  139. char sigType = buf[0x3];
  140. titleVersion = *(cpp3ds::Uint16*)&buf[dataOffsets[sigType] + 0x9C];
  141. contentCount = __builtin_bswap16(*(cpp3ds::Uint16*)&buf[dataOffsets[sigType] + 0x9E]);
  142. if (isResuming)
  143. installer->resume();
  144. bool foundIndex = false; // For resuming via contentIndex arg
  145. for (int i = 0; i < contentCount; ++i)
  146. {
  147. char *contentChunk = &buf[dataOffsets[sigType] + 0x9C4 + (i * 0x30)];
  148. cpp3ds::Uint32 contentId = __builtin_bswap32(*(cpp3ds::Uint32*)&contentChunk[0]);
  149. cpp3ds::Uint16 contentIdx = __builtin_bswap16(*(cpp3ds::Uint16*)&contentChunk[4]);
  150. cpp3ds::Uint64 contentSize = __builtin_bswap64(*(cpp3ds::Uint64*)&contentChunk[8]);
  151. titleFileSize += contentSize;
  152. contentIndices.push_back(contentIdx);
  153. if (contentIdx == contentIndex)
  154. {
  155. foundIndex = true;
  156. fileIndex = i + 1;
  157. }
  158. if (!isResuming || foundIndex)
  159. download->pushUrl(_("http://ccs.cdn.c.shop.nintendowifi.net/ccs/download/%016llX/%08lX", titleId, contentId), (contentIdx == contentIndex) ? installer->getCurrentContentPosition() : 0);
  160. }
  161. totalProcessed = progress * titleFileSize;
  162. if (!isResuming)
  163. {
  164. // Check for cancel at each stage in case it changes
  165. if (!download->isCanceled())
  166. {
  167. download->setProgressMessage(_("Installing ticket..."));
  168. if (!installer->installTicket(titleVersion))
  169. return false;
  170. }
  171. if (!download->isCanceled() && type == TitleKeys::Game && !app->getSeed().empty())
  172. {
  173. download->setProgressMessage(_("Installing seed..."));
  174. if (!installer->installSeed(&app->getSeed()[0]))
  175. return false;
  176. }
  177. if (!download->isCanceled())
  178. {
  179. if (!installer->start(true))
  180. return false;
  181. download->setProgressMessage(_("Installing TMD..."));
  182. if (!installer->installTmd(&buf[0], dataOffsets[sigType] + 0x9C4 + (contentCount * 0x30)))
  183. return false;
  184. if (!installer->finalizeTmd())
  185. return false;
  186. }
  187. }
  188. buf.clear();
  189. if (!isResuming)
  190. fileIndex++;
  191. }
  192. }
  193. else // is a Content file
  194. {
  195. // Conditions indicate download issue (e.g. internet is down)
  196. // with either an empty length or one not 64-byte aligned
  197. #ifdef _3DS
  198. if (len == 0 || len % 64 > 0)
  199. {
  200. cpp3ds::Lock lock(m_mutexRefresh);
  201. download->suspend();
  202. installer->suspend();
  203. return true;
  204. }
  205. #endif
  206. int oldIndex = installer->getCurrentContentIndex();
  207. if (!installer->installContent(data, len, contentIndices[fileIndex-1]))
  208. {
  209. if (download->getStatus() == Download::Suspended)
  210. return true;
  211. return false;
  212. }
  213. // Save index change to help recover queue from crash
  214. if (oldIndex != installer->getCurrentContentIndex())
  215. save();
  216. totalProcessed += len;
  217. download->setProgress(static_cast<double>(totalProcessed) / titleFileSize);
  218. if (processed == fileSize && fileSize != 0)
  219. {
  220. if (!installer->finalizeContent())
  221. return false;
  222. if (fileIndex == 1 && type == TitleKeys::DLC)
  223. {
  224. download->setProgressMessage(_("Importing content..."));
  225. if (!installer->importContents(contentIndices.size() - 1, &contentIndices[1]))
  226. return false;
  227. }
  228. fileIndex++;
  229. }
  230. }
  231. // Handle status message and counters
  232. count += len;
  233. float speed = count / clock.getElapsedTime().asSeconds() / 1024.f;
  234. int secsRemaining = (titleFileSize - totalProcessed) / 1024 / speed;
  235. if (fileIndex <= contentCount)
  236. download->setProgressMessage(_("Installing %d/%d... %.1f%% (%.0f KB/s) %dm %02ds",
  237. fileIndex, contentCount,
  238. download->getProgress() * 100.f,
  239. speed, secsRemaining / 60, secsRemaining % 60));
  240. if (clock.getElapsedTime() > cpp3ds::seconds(5.f))
  241. {
  242. count = 0;
  243. clock.restart();
  244. }
  245. return true;
  246. });
  247. download->setFinishCallback([=](bool canceled, bool failed) mutable
  248. {
  249. bool succeeded = false;
  250. switch (download->getStatus())
  251. {
  252. case Download::Suspended:
  253. download->setProgressMessage(_("Suspended"));
  254. if (download->getLastResponse().getStatus() == cpp3ds::Http::Response::NotFound)
  255. download->m_status = Download::Failed;
  256. else
  257. return;
  258. case Download::Finished:
  259. if (installer->commit())
  260. {
  261. Notification::spawn(_("Completed: %s", app->getTitle().toAnsiString().c_str()));
  262. download->setProgressMessage(_("Installed"));
  263. succeeded = true;
  264. #ifndef EMULATION
  265. if (Config::get(Config::NEWSDownloadFinished).GetBool()) {
  266. //Get the time to print it on Notification
  267. time_t t = time(NULL);
  268. struct tm * timeinfo;
  269. timeinfo = localtime(&t);
  270. char timeTextFmt[20];
  271. strftime(timeTextFmt, 20, "%d/%m/%Y %H:%M:%S", timeinfo);
  272. //Get the formatted game size
  273. std::string gameSize;
  274. if (app->getFilesize() > 1024 * 1024 * 1024)
  275. gameSize = _("%.1f GB", static_cast<float>(app->getFilesize()) / 1024.f / 1024.f / 1024.f).toAnsiString();
  276. else if (app->getFilesize() > 1024 * 1024)
  277. gameSize = _("%.1f MB", static_cast<float>(app->getFilesize()) / 1024.f / 1024.f).toAnsiString();
  278. else
  279. gameSize = _("%.1f KB", static_cast<float>(app->getFilesize()) / 1024).toAnsiString();
  280. //No blink functionnality
  281. bool canBlink = true;
  282. if (Config::get(Config::NEWSNoLED).GetBool() || Config::get(Config::LEDDownloadFinished).GetBool())
  283. canBlink = false;
  284. Notification::sendNews(_("%s has been installed", app->getTitle().toAnsiString().c_str()), _("The game %s (%s) has been installed on \n%s", app->getTitle().toAnsiString().c_str(), gameSize, timeTextFmt));
  285. if (!canBlink)
  286. MCU::getInstance().ledReset();
  287. }
  288. if (Config::get(Config::LEDDownloadFinished).GetBool())
  289. MCU::getInstance().ledBlinkOnce(0xFFA419);
  290. #endif
  291. break;
  292. }
  293. download->m_status = Download::Failed;
  294. // Fall through
  295. case Download::Failed:
  296. Notification::spawn(_("Failed: %s", app->getTitle().toAnsiString().c_str()));
  297. installer->abort();
  298. switch (installer->getErrorCode()) {
  299. case 0: // Failed due to internet problem and not from Installer
  300. download->setProgressMessage(_("Failed: Internet problem"));
  301. break;
  302. case 0xC8A08035: download->setProgressMessage(_("Not enough space on NAND")); break;
  303. case 0xC86044D2: download->setProgressMessage(_("Not enough space on SD")); break;
  304. case 0xD8E0806A: download->setProgressMessage(_("Wrong title key")); break;
  305. case 0xD8A08004: download->setProgressMessage(_("Incomplete installation")); break;
  306. default:
  307. download->setProgressMessage(installer->getErrorString());
  308. }
  309. break;
  310. case Download::Canceled:
  311. break;
  312. }
  313. download->setProgress(1.f);
  314. // Reset CanSendTop state
  315. for (auto& download : m_downloads)
  316. download->download->m_canSendTop = true;
  317. // Refresh installed list to add recent install
  318. if (succeeded)
  319. InstalledList::getInstance().refresh();
  320. // Play sound if queue is finished
  321. if (getActiveCount() == 0 && !canceled)
  322. if ((Config::get(Config::PlaySoundAfterDownload).GetBool() && !SleepState::isSleeping) || (SleepState::isSleeping && Config::get(Config::PlaySoundAfterDownload).GetBool() && Config::get(Config::SoundOnInactivity).GetBool()))
  323. m_soundFinish.play();
  324. if (callback)
  325. callback(succeeded);
  326. });
  327. if (progress > 0.f)
  328. {
  329. download->setProgress(progress);
  330. download->setProgressMessage(_("Suspended"));
  331. download->m_status = Download::Suspended;
  332. }
  333. else
  334. download->setProgressMessage(_("Queued"));
  335. std::unique_ptr<DownloadItem> downloadItem(new DownloadItem(app, download, installer));
  336. m_downloads.emplace_back(std::move(downloadItem));
  337. realign();
  338. }
  339. void DownloadQueue::draw(cpp3ds::RenderTarget &target, cpp3ds::RenderStates states) const
  340. {
  341. states.transform *= getTransform();
  342. states.scissor = cpp3ds::UintRect(0, 30, 320, 210);
  343. for (auto& item : m_downloads)
  344. {
  345. static float top = 30.f - item->download->getSize().y;
  346. float posY = item->download->getPosition().y + m_scrollPos;
  347. if (posY > top && posY < 240.f)
  348. target.draw(*item->download, states);
  349. }
  350. }
  351. void DownloadQueue::restartDownload(Download *download)
  352. {
  353. for (auto& item : m_downloads)
  354. if (item->download == download && item->download->getStatus() == Download::Failed)
  355. {
  356. item->download->m_markedForDelete = true;
  357. addDownload(item->appItem, item->installer->getTitleId());
  358. break;
  359. }
  360. }
  361. bool DownloadQueue::isDownloading(std::shared_ptr<AppItem> app)
  362. {
  363. for (auto& item : m_downloads)
  364. if (item->appItem == app)
  365. {
  366. auto status = item->download->getStatus();
  367. if (status == Download::Queued || status == Download::Downloading || status == Download::Suspended)
  368. return true;
  369. }
  370. return false;
  371. }
  372. bool DownloadQueue::isDownloading(cpp3ds::Uint64 titleId)
  373. {
  374. for (auto& item : m_downloads)
  375. if (item->installer->getTitleId() == titleId)
  376. {
  377. auto status = item->download->getStatus();
  378. if (status == Download::Queued || status == Download::Downloading || status == Download::Suspended)
  379. return true;
  380. }
  381. return false;
  382. }
  383. bool DownloadQueue::processEvent(const cpp3ds::Event &event)
  384. {
  385. for (auto& item : m_downloads)
  386. {
  387. static float top = 30.f - item->download->getSize().y;
  388. float posY = item->download->getPosition().y + m_scrollPos;
  389. if (posY > top && posY < 240.f)
  390. item->download->processEvent(event);
  391. }
  392. return true;
  393. }
  394. void DownloadQueue::realign()
  395. {
  396. bool processedFirstItem = false;
  397. m_size.y = 0.f;
  398. for (int i = 0; i < m_downloads.size(); ++i)
  399. {
  400. Download *download = m_downloads[i]->download;
  401. Download::Status status = download->getStatus();
  402. if (download->markedForDelete())
  403. continue;
  404. if (processedFirstItem)
  405. {
  406. download->m_canSendTop = (status == Download::Queued || status == Download::Suspended || status == Download::Downloading);
  407. }
  408. else if (status == Download::Queued || status == Download::Suspended || status == Download::Downloading)
  409. {
  410. processedFirstItem = true;
  411. download->m_canSendTop = false;
  412. }
  413. TweenEngine::Tween::to(*download, util3ds::TweenSprite::POSITION_Y, 0.2f)
  414. .target(33.f + i * download->getSize().y)
  415. .start(m_tweenManager);
  416. m_size.y += download->getSize().y;
  417. }
  418. updateScrollSize();
  419. save();
  420. }
  421. void DownloadQueue::update(float delta)
  422. {
  423. // Remove downloads marked for delete
  424. {
  425. bool changed = false;
  426. cpp3ds::Lock lock(m_mutexRefresh);
  427. for (auto it = m_downloads.begin(); it != m_downloads.end();)
  428. {
  429. Download *download = it->get()->download;
  430. if (download->markedForDelete())
  431. {
  432. if (download->getStatus() == Download::Suspended)
  433. it->get()->installer->abort();
  434. m_downloads.erase(it);
  435. changed = true;
  436. }
  437. else
  438. ++it;
  439. if (!download->markedForDelete())
  440. download->update(delta);
  441. }
  442. if (changed)
  443. realign();
  444. }
  445. m_tweenManager.update(delta);
  446. }
  447. size_t DownloadQueue::getCount()
  448. {
  449. return m_downloads.size();
  450. }
  451. size_t DownloadQueue::getActiveCount()
  452. {
  453. size_t count = 0;
  454. for (auto& item : m_downloads)
  455. if (item->download->getProgress() < 1.f)
  456. ++count;
  457. return count;
  458. }
  459. DownloadQueue &DownloadQueue::getInstance()
  460. {
  461. static DownloadQueue downloadQueue;
  462. return downloadQueue;
  463. }
  464. void DownloadQueue::sendTop(Download *download)
  465. {
  466. cpp3ds::Lock lock(m_mutexRefresh);
  467. auto iterTopDownload = m_downloads.end();
  468. auto iterBottomDownload = m_downloads.end();
  469. for (auto it = m_downloads.begin(); it != m_downloads.end(); ++it)
  470. {
  471. if (iterTopDownload == m_downloads.end())
  472. {
  473. Download::Status status = it->get()->download->getStatus();
  474. if (status == Download::Downloading || status == Download::Queued || status == Download::Suspended)
  475. iterTopDownload = it;
  476. }
  477. else if (iterBottomDownload == m_downloads.end())
  478. {
  479. if (it->get()->download == download)
  480. iterBottomDownload = it;
  481. }
  482. }
  483. if (iterTopDownload != m_downloads.end() && iterBottomDownload != m_downloads.end())
  484. {
  485. std::rotate(iterTopDownload, iterBottomDownload, iterBottomDownload + 1);
  486. realign();
  487. m_clockRefresh.restart();
  488. }
  489. }
  490. void DownloadQueue::refresh()
  491. {
  492. while (!m_refreshEnd)
  493. {
  494. {
  495. cpp3ds::Lock lock(m_mutexRefresh);
  496. DownloadItem *activeDownload = nullptr;
  497. DownloadItem *firstQueued = nullptr;
  498. bool activeNeedsSuspension = false;
  499. for (auto& item : m_downloads)
  500. {
  501. Download::Status status = item->download->getStatus();
  502. if (!firstQueued && (status == Download::Queued || status == Download::Suspended))
  503. {
  504. firstQueued = item.get();
  505. }
  506. else if (!activeDownload && status == Download::Downloading)
  507. {
  508. activeDownload = item.get();
  509. if (firstQueued)
  510. activeNeedsSuspension = true;
  511. }
  512. }
  513. if ((!activeDownload && firstQueued) || activeNeedsSuspension)
  514. {
  515. if (activeNeedsSuspension)
  516. {
  517. activeDownload->download->suspend();
  518. activeDownload->installer->suspend();
  519. }
  520. firstQueued->installer->resume();
  521. firstQueued->download->resume();
  522. realign();
  523. }
  524. }
  525. m_clockRefresh.restart();
  526. while (m_clockRefresh.getElapsedTime() < cpp3ds::seconds(1.f))
  527. cpp3ds::sleep(cpp3ds::milliseconds(200));
  528. }
  529. }
  530. void DownloadQueue::suspend()
  531. {
  532. cpp3ds::Lock lock(m_mutexRefresh);
  533. m_refreshEnd = true;
  534. if (m_isSleepInstalling)
  535. m_sleepInstaller->suspend();
  536. for (auto& item : m_downloads)
  537. {
  538. item->download->suspend();
  539. item->installer->suspend();
  540. }
  541. }
  542. void DownloadQueue::resume()
  543. {
  544. m_refreshEnd = false;
  545. if (m_isSleepInstalling)
  546. m_sleepInstaller->resume();
  547. m_threadRefresh.launch();
  548. }
  549. void DownloadQueue::save()
  550. {
  551. rapidjson::Document json;
  552. std::string filepath = cpp3ds::FileSystem::getFilePath(FREESHOP_DIR "/queue.json");
  553. std::ofstream file(filepath);
  554. rapidjson::OStreamWrapper osw(file);
  555. rapidjson::Writer<rapidjson::OStreamWrapper> writer(osw);
  556. std::vector<std::string> subTitleIds; // Strings need to be in memory for json allocator write
  557. json.SetArray();
  558. for (auto& item : m_downloads)
  559. {
  560. Download::Status status = item->download->getStatus();
  561. if (status != Download::Downloading && status != Download::Queued && status != Download::Suspended)
  562. continue;
  563. rapidjson::Value obj(rapidjson::kObjectType);
  564. cpp3ds::Uint32 titleType = item->installer->getTitleId() >> 32;
  565. subTitleIds.push_back(_("%016llX", item->installer->getTitleId()).toAnsiString());
  566. obj.AddMember("title_id", rapidjson::StringRef(item->appItem->getTitleIdStr().c_str()), json.GetAllocator());
  567. obj.AddMember("subtitle_id", rapidjson::StringRef(subTitleIds.back().c_str()), json.GetAllocator());
  568. // TODO: Figure out how to properly resume DLC installation
  569. if (titleType == TitleKeys::DLC) {
  570. obj.AddMember("content_index", rapidjson::Value().SetInt(-1), json.GetAllocator());
  571. obj.AddMember("progress", rapidjson::Value().SetFloat(0.f), json.GetAllocator());
  572. } else {
  573. obj.AddMember("content_index", rapidjson::Value().SetInt(status == Download::Queued ? -1 : item->installer->getCurrentContentIndex()), json.GetAllocator());
  574. obj.AddMember("progress", rapidjson::Value().SetFloat(item->download->getProgress()), json.GetAllocator());
  575. }
  576. json.PushBack(obj, json.GetAllocator());
  577. }
  578. json.Accept(writer);
  579. }
  580. void DownloadQueue::load()
  581. {
  582. uint32_t pendingTitleCountSD = 0;
  583. uint32_t pendingTitleCountNAND = 0;
  584. std::vector<uint64_t> pendingTitleIds;
  585. #ifndef EMULATION
  586. Result res = 0;
  587. if (R_SUCCEEDED(res = AM_GetPendingTitleCount(&pendingTitleCountSD, MEDIATYPE_SD, AM_STATUS_MASK_INSTALLING)))
  588. if (R_SUCCEEDED(res = AM_GetPendingTitleCount(&pendingTitleCountNAND, MEDIATYPE_NAND, AM_STATUS_MASK_INSTALLING)))
  589. {
  590. pendingTitleIds.resize(pendingTitleCountSD + pendingTitleCountNAND);
  591. res = AM_GetPendingTitleList(nullptr, pendingTitleCountSD, MEDIATYPE_SD, AM_STATUS_MASK_INSTALLING, &pendingTitleIds[0]);
  592. res = AM_GetPendingTitleList(nullptr, pendingTitleCountNAND, MEDIATYPE_NAND, AM_STATUS_MASK_INSTALLING, &pendingTitleIds[pendingTitleCountSD]);
  593. }
  594. #endif
  595. cpp3ds::FileInputStream file;
  596. if (file.open(FREESHOP_DIR "/queue.json"))
  597. {
  598. rapidjson::Document json;
  599. std::string jsonStr;
  600. jsonStr.resize(file.getSize());
  601. file.read(&jsonStr[0], jsonStr.size());
  602. json.Parse(jsonStr.c_str());
  603. if (!json.HasParseError())
  604. {
  605. auto &list = AppList::getInstance().getList();
  606. for (auto it = json.Begin(); it != json.End(); ++it)
  607. {
  608. auto item = it->GetObject();
  609. std::string strTitleId = item["title_id"].GetString();
  610. std::string strSubTitleId = item["subtitle_id"].GetString();
  611. int contentIndex = item["content_index"].GetInt();
  612. float progress = item["progress"].GetFloat();
  613. uint64_t titleId = strtoull(strTitleId.c_str(), 0, 16);
  614. uint64_t subTitleId = strtoull(strSubTitleId.c_str(), 0, 16);
  615. size_t parentType = titleId >> 32;
  616. #ifdef _3DS
  617. for (auto pendingTitleId : pendingTitleIds)
  618. if (contentIndex == -1 || pendingTitleId == titleId || pendingTitleId == subTitleId)
  619. {
  620. #endif
  621. // For system titles, the relevant AppItem won't be in AppList
  622. if (parentType == TitleKeys::SystemApplet || parentType == TitleKeys::SystemApplication)
  623. for (auto &app : InstalledList::getInstance().getList())
  624. {
  625. if (app->getTitleId() == titleId)
  626. addDownload(app->getAppItem(), subTitleId, nullptr, contentIndex, progress);
  627. }
  628. else
  629. for (auto &app : list)
  630. if (app->getAppItem()->getTitleId() == titleId)
  631. addDownload(app->getAppItem(), subTitleId, nullptr, contentIndex, progress);
  632. #ifdef _3DS
  633. break;
  634. }
  635. #endif
  636. }
  637. }
  638. }
  639. }
  640. void DownloadQueue::addSleepDownload(std::shared_ptr<AppItem> app, cpp3ds::Uint64 titleId, cpp3ds::String title)
  641. {
  642. #ifndef EMULATION
  643. //Lock the thread so if user wants to install another app while an app is processed, this doesn't fu** up everything (and make ARM9 crashes (true story))
  644. cpp3ds::Lock lock(m_mutexSleepInstall);
  645. //A sleep installation is processed
  646. m_isSleepInstalling = true;
  647. //Get title type
  648. cpp3ds::Uint32 type = titleId >> 32;
  649. // Check if enough space to install the title
  650. FS_ArchiveResource resource = {0};
  651. Result ret;
  652. if (type == TitleKeys::DSiWare)
  653. ret = FSUSER_GetArchiveResource(&resource, SYSTEM_MEDIATYPE_TWL_NAND);
  654. else
  655. ret = FSUSER_GetArchiveResource(&resource, SYSTEM_MEDIATYPE_SD);
  656. if(R_SUCCEEDED(ret)) {
  657. u64 size = (u64) resource.freeClusters * (u64) resource.clusterSize;
  658. if (app->getFilesize() > size) {
  659. if (Config::get(Config::LEDDownloadError).GetBool())
  660. MCU::getInstance().ledBlinkThrice(0x1A25FF);
  661. if (type == TitleKeys::DSiWare)
  662. Notification::spawn(_("Not enough space on NAND to install: \n%s", app->getTitle().toAnsiString().c_str()));
  663. else
  664. Notification::spawn(_("Not enough space on SD card to install: \n%s", app->getTitle().toAnsiString().c_str()));
  665. return;
  666. }
  667. }
  668. if (titleId == 0)
  669. {
  670. if (app->isInstalled()) { // Don't allow reinstalling without deleting
  671. m_isSleepInstalling = false;
  672. return;
  673. }
  674. titleId = app->getTitleId();
  675. }
  676. bool isRegistered;
  677. NIMS_IsTaskRegistered(titleId, &isRegistered);
  678. //Get title name
  679. cpp3ds::String appTitle;
  680. if (title == "")
  681. appTitle = app->getTitle();
  682. else
  683. appTitle = title;
  684. //Add prefix to the game
  685. if (type == TitleKeys::Update)
  686. appTitle = _("[Update] %s", appTitle.toAnsiString().c_str());
  687. else if (type == TitleKeys::Demo)
  688. appTitle = _("[Demo] %s", appTitle.toAnsiString().c_str());
  689. else if (type == TitleKeys::DLC)
  690. appTitle = _("[DLC] %s", appTitle.toAnsiString().c_str());
  691. std::string stdAppTitle = appTitle.toAnsiString();
  692. if (isRegistered) {
  693. Notification::spawn(_("Ready for sleep installation: \n%s", app->getTitle().toAnsiString().c_str()));
  694. m_isSleepInstalling = false;
  695. return;
  696. }
  697. Notification::spawn(_("Processing sleep installation: \n%s", stdAppTitle.c_str()));
  698. //Some vars initialisation
  699. uint32_t res;
  700. NIM_TitleConfig tc;
  701. m_sleepInstaller = new Installer(titleId, 0);
  702. FS_MediaType mediaType = ((titleId >> 32) == TitleKeys::DSiWare) ? MEDIATYPE_NAND : MEDIATYPE_SD;
  703. cpp3ds::Uint32 titleType = titleId >> 32;
  704. //Get the title version (and not ticket version) via the title ID
  705. res = getTicketVersion(titleId);
  706. printf("%" PRIu32 "\n", res);
  707. //Check at each step that the user didn't went to HOME Menu
  708. if (m_refreshEnd) {
  709. while (m_refreshEnd)
  710. cpp3ds::sleep(cpp3ds::milliseconds(100));
  711. }
  712. //Install app ticket
  713. if (!m_sleepInstaller->installTicket(res)) {
  714. Notification::spawn(_("Can't install ticket: \n%s", stdAppTitle.c_str()));
  715. m_isSleepInstalling = false;
  716. return;
  717. }
  718. //Check at each step that the user didn't went to HOME Menu
  719. if (m_refreshEnd) {
  720. while (m_refreshEnd)
  721. cpp3ds::sleep(cpp3ds::milliseconds(100));
  722. }
  723. //Install app seed
  724. if (titleType == TitleKeys::Game && !app->getSeed().empty()) {
  725. if (!m_sleepInstaller->installSeed(&app->getSeed()[0])) {
  726. Notification::spawn(_("Can't install seed: \n%s", stdAppTitle.c_str()));
  727. m_isSleepInstalling = false;
  728. return;
  729. }
  730. }
  731. //Check at each step that the user didn't went to HOME Menu
  732. if (m_refreshEnd) {
  733. while (m_refreshEnd)
  734. cpp3ds::sleep(cpp3ds::milliseconds(100));
  735. }
  736. //The code that register the sleep download
  737. NIMS_MakeTitleConfig(&tc, titleId, res, 0, mediaType);
  738. if (R_SUCCEEDED(res = NIMS_RegisterTask(&tc, stdAppTitle.c_str(), "freeShop download"))) {
  739. Notification::spawn(_("Ready for sleep installation: \n%s", stdAppTitle.c_str()));
  740. if (Config::get(Config::LEDDownloadFinished).GetBool())
  741. MCU::getInstance().ledBlinkOnce(0x8EEBA0);
  742. } else {
  743. Notification::spawn(_("Sleep installation failed: \n%s", stdAppTitle.c_str()));
  744. if (Config::get(Config::LEDDownloadError).GetBool())
  745. MCU::getInstance().ledBlinkThrice(0x1A25FF);
  746. }
  747. /*The clear and simple explanation of this (LOOOONNNGGGGGGG) condition:
  748. If the "Play sound after downoad queue finishes" option IS enabled AND the freeShop is NOT on it's sleeping state, the sound can be played
  749. If the "Play sound after downoad queue finishes" option IS enabled AND the freeShop IS on it's sleeping state AND the "Allow sounds on inactivity mode" option IS enabled, the sound can be played*/
  750. if ((Config::get(Config::PlaySoundAfterDownload).GetBool() && !SleepState::isSleeping) || (SleepState::isSleeping && Config::get(Config::PlaySoundAfterDownload).GetBool() && Config::get(Config::SoundOnInactivity).GetBool()))
  751. m_soundFinish.play();
  752. m_isSleepInstalling = false;
  753. #endif
  754. }
  755. void DownloadQueue::setScroll(float position)
  756. {
  757. m_scrollPos = position;
  758. setPosition(0.f, std::round(position));
  759. }
  760. float DownloadQueue::getScroll()
  761. {
  762. return m_scrollPos;
  763. }
  764. const cpp3ds::Vector2f &DownloadQueue::getScrollSize()
  765. {
  766. return m_size;
  767. }
  768. } // namespace FreeShop