AppItem.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485
  1. #include <cpp3ds/System/Err.hpp>
  2. #include <iostream>
  3. #include <cpp3ds/System/I18n.hpp>
  4. #include "AssetManager.hpp"
  5. #include "AppItem.hpp"
  6. #include "Util.hpp"
  7. #include "TitleKeys.hpp"
  8. #include "Theme.hpp"
  9. #include "DownloadQueue.hpp"
  10. #include <sstream>
  11. #include <unistd.h>
  12. #include "Notification.hpp"
  13. #include "Installer.hpp"
  14. #include "States/BrowseState.hpp"
  15. #include "States/DialogState.hpp"
  16. #ifndef EMULATION
  17. #include <3ds.h>
  18. #endif
  19. namespace {
  20. std::vector<std::unique_ptr<cpp3ds::Texture>> textures;
  21. #ifdef _3DS
  22. typedef struct {
  23. u16 shortDescription[0x40];
  24. u16 longDescription[0x80];
  25. u16 publisher[0x40];
  26. } SMDH_title;
  27. typedef struct {
  28. char magic[0x04];
  29. u16 version;
  30. u16 reserved1;
  31. SMDH_title titles[0x10];
  32. u8 ratings[0x10];
  33. u32 region;
  34. u32 matchMakerId;
  35. u64 matchMakerBitId;
  36. u32 flags;
  37. u16 eulaVersion;
  38. u16 reserved;
  39. u32 optimalBannerFrame;
  40. u32 streetpassId;
  41. u64 reserved2;
  42. u8 smallIcon[0x480];
  43. u8 largeIcon[0x1200];
  44. } SMDH;
  45. #endif
  46. }
  47. namespace FreeShop
  48. {
  49. AppItem::AppItem()
  50. : m_installed(false)
  51. , m_regions(0)
  52. , m_languages(0)
  53. , m_iconIndex(-1)
  54. , m_iconRect(cpp3ds::IntRect(0, 0, 48, 48))
  55. , m_systemIconTexture(nullptr)
  56. , m_threadSleepInstall(&AppItem::queueForSleepInstallThread, this)
  57. , m_sleepInstallDemo(false)
  58. , m_sleepInstallRelated(false)
  59. {
  60. if (Theme::isMissingIconThemed) {
  61. cpp3ds::Texture &texture = AssetManager<cpp3ds::Texture>::get(FREESHOP_DIR "/theme/images/missing-icon.png");
  62. texture.setSmooth(true);
  63. m_iconTexture = &texture;
  64. } else {
  65. cpp3ds::Texture &texture = AssetManager<cpp3ds::Texture>::get("images/missing-icon.png");
  66. texture.setSmooth(true);
  67. m_iconTexture = &texture;
  68. }
  69. }
  70. AppItem::~AppItem()
  71. {
  72. m_threadSleepInstall.wait();
  73. delete m_systemIconTexture;
  74. }
  75. const cpp3ds::String &AppItem::getTitle() const
  76. {
  77. return m_title;
  78. }
  79. void AppItem::loadFromJSON(const char* titleId, const rapidjson::Value &json)
  80. {
  81. m_genres.clear();
  82. m_features.clear();
  83. m_seed.clear();
  84. m_languages = 0;
  85. m_titleIdStr = titleId;
  86. m_titleId = strtoull(titleId, 0, 16);
  87. m_updates = TitleKeys::getRelated(m_titleId, TitleKeys::Update);
  88. m_demos = TitleKeys::getRelated(m_titleId, TitleKeys::Demo);
  89. m_dlc = TitleKeys::getRelated(m_titleId, TitleKeys::DLC);
  90. m_isSleepBusy = false;
  91. const char *title = json[0].GetString();
  92. m_title = cpp3ds::String::fromUtf8(title, title + json[0].GetStringLength());
  93. m_normalizedTitle = json[1].GetString();
  94. m_contentId = json[2].GetString();
  95. m_regions = json[3].GetInt();
  96. m_uriRegion = json[4].GetString();
  97. m_filesize = json[5].GetUint64();
  98. int iconIndex = json[6].GetInt();
  99. if (iconIndex >= 0)
  100. setIconIndex(iconIndex);
  101. // Crypto seed
  102. std::string seed = json[7].GetString();
  103. if (!seed.empty())
  104. {
  105. m_seed.clear();
  106. for (int i = 0; i < 16; ++i)
  107. {
  108. std::string byteStr = seed.substr(i*2, 2);
  109. char byte = strtol(byteStr.c_str(), NULL, 16);
  110. m_seed.push_back(byte);
  111. }
  112. }
  113. // Genres
  114. auto genres = json[8].GetArray();
  115. for (int i = 0; i < genres.Size(); ++i)
  116. m_genres.push_back(genres[i].GetInt());
  117. // Languages
  118. auto languages = json[9].GetArray();
  119. for (int i = 0; i < languages.Size(); ++i)
  120. {
  121. std::string lang(languages[i].GetString());
  122. if ("ja" == lang) m_languages |= Japanese;
  123. else if ("en" == lang) m_languages |= English;
  124. else if ("es" == lang) m_languages |= Spanish;
  125. else if ("fr" == lang) m_languages |= French;
  126. else if ("de" == lang) m_languages |= German;
  127. else if ("it" == lang) m_languages |= Italian;
  128. else if ("nl" == lang) m_languages |= Dutch;
  129. else if ("pt" == lang) m_languages |= Portuguese;
  130. else if ("ru" == lang) m_languages |= Russian;
  131. }
  132. // Features
  133. auto features = json[10].GetArray();
  134. for (int i = 0; i < features.Size(); ++i)
  135. m_features.push_back(features[i].GetInt());
  136. if (!json[11].IsNull())
  137. m_voteScore = json[11].GetFloat();
  138. m_voteCount = json[12].GetInt();
  139. m_releaseDate = json[13].GetInt();
  140. m_productCode = json[14].GetString();
  141. m_platform = json[15].GetInt();
  142. m_publisher = json[16].GetInt();
  143. }
  144. void AppItem::loadFromSystemTitleId(cpp3ds::Uint64 titleId)
  145. {
  146. m_titleId = titleId;
  147. m_titleIdStr = _("%016llX", titleId);
  148. m_updates = TitleKeys::getRelated(m_titleId, TitleKeys::Update);
  149. m_demos = TitleKeys::getRelated(m_titleId, TitleKeys::Demo);
  150. m_dlc = TitleKeys::getRelated(m_titleId, TitleKeys::DLC);
  151. #ifdef _3DS
  152. Result res = 0;
  153. Handle fileHandle;
  154. char productCode[12] = {'\0'};
  155. AM_TitleEntry titleInfo;
  156. AM_GetTitleInfo(MEDIATYPE_NAND, 1, &titleId, &titleInfo);
  157. AM_GetTitleProductCode(MEDIATYPE_NAND, titleId, productCode);
  158. m_title = productCode;
  159. if (m_title.toAnsiString().compare(0, 9, "CTR-N-HMM") == 0)
  160. m_title = _("Home Menu Themes");
  161. static const u32 filePath[5] = {0x0, 0x0, 0x2, 0x6E6F6369, 0x0};
  162. u32 archivePath[4] = {(u32) (titleId & 0xFFFFFFFF), (u32) ((titleId >> 32) & 0xFFFFFFFF), MEDIATYPE_NAND, 0x0};
  163. FS_Path binArchPath = {PATH_BINARY, 0x10, archivePath};
  164. FS_Path binFilePath = {PATH_BINARY, 0x14, filePath};
  165. if(R_SUCCEEDED(res = FSUSER_OpenFileDirectly(&fileHandle, ARCHIVE_SAVEDATA_AND_CONTENT, binArchPath, binFilePath, FS_OPEN_READ, 0)))
  166. {
  167. auto smdh = new SMDH();
  168. if (smdh)
  169. {
  170. FSFILE_Read(fileHandle, nullptr, 0, smdh, sizeof(SMDH));
  171. if (smdh->magic[0] == 'S' && smdh->magic[1] == 'M' && smdh->magic[2] == 'D' && smdh->magic[3] == 'H')
  172. {
  173. u8 systemLanguage = CFG_LANGUAGE_EN;
  174. CFGU_GetSystemLanguage(&systemLanguage);
  175. u16 *title = smdh->titles[systemLanguage].shortDescription;
  176. m_title = cpp3ds::String::fromUtf16(title, title + 0x40);
  177. delete m_systemIconTexture;
  178. m_systemIconTexture = new cpp3ds::Texture();
  179. m_systemIconTexture->loadFromPreprocessedMemory(smdh->largeIcon, sizeof(smdh->largeIcon), 48, 48, GPU_RGB565);
  180. }
  181. delete smdh;
  182. }
  183. FSFILE_Close(fileHandle);
  184. }
  185. #else
  186. m_title = m_titleIdStr;
  187. #endif
  188. m_normalizedTitle = m_title;
  189. for (auto &c : m_normalizedTitle)
  190. c = std::tolower(c);
  191. }
  192. void AppItem::setIconIndex(size_t iconIndex)
  193. {
  194. size_t textureIndex = iconIndex / 441;
  195. iconIndex %= 441;
  196. // Load texture from file if not done already
  197. if (textures.size() < textureIndex + 1)
  198. {
  199. for (int i = textures.size(); i <= textureIndex; ++i)
  200. {
  201. std::unique_ptr<cpp3ds::Texture> texture(new cpp3ds::Texture());
  202. #ifdef EMULATION
  203. texture->loadFromFile(_(FREESHOP_DIR "/cache/images/icons%d.jpg", i));
  204. #else
  205. texture->loadFromPreprocessedFile(_(FREESHOP_DIR "/cache/images/icons%d.bin", i), 1024, 1024, GPU_ETC1);
  206. #endif
  207. texture->setSmooth(true);
  208. textures.push_back(std::move(texture));
  209. }
  210. }
  211. m_iconIndex = iconIndex;
  212. m_iconTexture = textures[textureIndex].get();
  213. int x = (iconIndex / 21) * 48;
  214. int y = (iconIndex % 21) * 48;
  215. m_iconRect = cpp3ds::IntRect(x, y, 48, 48);
  216. }
  217. bool AppItem::isCached() const
  218. {
  219. return pathExists(getJsonFilename().c_str());
  220. }
  221. const std::string AppItem::getJsonFilename() const
  222. {
  223. std::string filename = FREESHOP_DIR "/tmp/" + getTitleIdStr() + "/data.json";
  224. return filename;
  225. }
  226. void AppItem::setInstalled(bool installed)
  227. {
  228. m_installed = installed;
  229. }
  230. bool AppItem::isInstalled() const
  231. {
  232. return m_installed;
  233. }
  234. cpp3ds::Uint64 AppItem::getFilesize() const
  235. {
  236. return m_filesize;
  237. }
  238. const std::string &AppItem::getNormalizedTitle() const
  239. {
  240. return m_normalizedTitle;
  241. }
  242. const std::string &AppItem::getTitleIdStr() const
  243. {
  244. return m_titleIdStr;
  245. }
  246. cpp3ds::Uint64 AppItem::getTitleId() const
  247. {
  248. return m_titleId;
  249. }
  250. const std::string &AppItem::getContentId() const
  251. {
  252. return m_contentId;
  253. }
  254. const std::string &AppItem::getUriRegion() const
  255. {
  256. return m_uriRegion;
  257. }
  258. int AppItem::getRegions() const
  259. {
  260. return m_regions;
  261. }
  262. int AppItem::getLanguages() const
  263. {
  264. return m_languages;
  265. }
  266. const std::vector<char> &AppItem::getSeed() const
  267. {
  268. return m_seed;
  269. }
  270. const std::vector<int> &AppItem::getGenres() const
  271. {
  272. return m_genres;
  273. }
  274. const std::vector<int> &AppItem::getFeatures() const
  275. {
  276. return m_features;
  277. }
  278. int AppItem::getPlatform() const
  279. {
  280. return m_platform;
  281. }
  282. int AppItem::getPublisher() const
  283. {
  284. return m_publisher;
  285. }
  286. int AppItem::getIconIndex() const
  287. {
  288. return m_iconIndex;
  289. }
  290. const cpp3ds::Texture *AppItem::getIcon(cpp3ds::IntRect &outRect) const
  291. {
  292. outRect = m_iconRect;
  293. return m_systemIconTexture ? : m_iconTexture;
  294. }
  295. void AppItem::queueForInstall()
  296. {
  297. DownloadQueue::getInstance().addDownload(shared_from_this());
  298. for (auto &id : m_updates)
  299. DownloadQueue::getInstance().addDownload(shared_from_this(), id);
  300. for (auto &id : m_dlc)
  301. DownloadQueue::getInstance().addDownload(shared_from_this(), id);
  302. }
  303. void AppItem::queueForSleepInstall(bool installRelated, bool isDemo)
  304. {
  305. #ifndef EMULATION
  306. if (g_isLatestFirmwareVersion) {
  307. if (m_isSleepBusy) {
  308. Notification::spawn(_("Already queued for sleep installation: \n%s", m_title.toAnsiString().c_str()));
  309. } else {
  310. m_sleepInstallRelated = installRelated;
  311. m_sleepInstallDemo = isDemo;
  312. m_threadSleepInstall.launch();
  313. }
  314. } else {
  315. Notification::spawn(_("A system update is required to download in sleep mode."));
  316. }
  317. #endif
  318. }
  319. void AppItem::queueForSleepInstallThread()
  320. {
  321. #ifndef EMULATION
  322. m_isSleepBusy = true;
  323. Notification::spawn(_("Preparing for sleep installation: \n%s", m_title.toAnsiString().c_str()));
  324. if (!m_sleepInstallDemo)
  325. DownloadQueue::getInstance().addSleepDownload(shared_from_this(), m_titleId, m_title);
  326. else
  327. DownloadQueue::getInstance().addSleepDownload(shared_from_this(), getDemos()[0], m_title);
  328. if (m_sleepInstallRelated) {
  329. for (auto &id : m_updates)
  330. DownloadQueue::getInstance().addSleepDownload(shared_from_this(), id, m_title);
  331. for (auto &id : m_dlc)
  332. DownloadQueue::getInstance().addSleepDownload(shared_from_this(), id, m_title);
  333. }
  334. m_isSleepBusy = false;
  335. #endif
  336. }
  337. void AppItem::removeSleepInstall(bool removeRelated)
  338. {
  339. #ifndef EMULATION
  340. m_removeSleepRelated = removeRelated;
  341. askUserToRemove(m_titleId, m_title);
  342. if (m_removeSleepRelated) {
  343. for (auto &id : m_updates)
  344. askUserToRemove(id, _("[Update] %s", m_title.toAnsiString().c_str()));
  345. for (auto &id : m_dlc)
  346. askUserToRemove(id, _("[DLC] %s", m_title.toAnsiString().c_str()));
  347. }
  348. #endif
  349. }
  350. void AppItem::askUserToRemove(cpp3ds::Uint64 titleID, cpp3ds::String titleName)
  351. {
  352. #ifndef EMULATION
  353. g_browseState->requestStackPush(States::Dialog, false, [=](void *data) mutable {
  354. auto event = reinterpret_cast<DialogState::Event*>(data);
  355. if (event->type == DialogState::GetText)
  356. {
  357. auto str = reinterpret_cast<cpp3ds::String*>(event->data);
  358. *str = _("%s\n\nAre you sure you want to cancel this installation and lose all progress?", titleName.toAnsiString().c_str());
  359. return true;
  360. }
  361. else if (event->type == DialogState::GetTitle) {
  362. auto str = reinterpret_cast<cpp3ds::String *>(event->data);
  363. *str = _("Cancel a download");
  364. return true;
  365. }
  366. else if (event->type == DialogState::Response)
  367. {
  368. bool *accepted = reinterpret_cast<bool*>(event->data);
  369. if (*accepted)
  370. {
  371. NIMS_UnregisterTask(titleID);
  372. Notification::spawn(_("Sleep download canceled: \n%s", titleName.toAnsiString().c_str()));
  373. }
  374. return true;
  375. }
  376. return false;
  377. });
  378. #endif
  379. }
  380. const std::vector<cpp3ds::Uint64> &AppItem::getUpdates() const
  381. {
  382. return m_updates;
  383. }
  384. const std::vector<cpp3ds::Uint64> &AppItem::getDemos() const
  385. {
  386. return m_demos;
  387. }
  388. const std::vector<cpp3ds::Uint64> &AppItem::getDLC() const
  389. {
  390. return m_dlc;
  391. }
  392. float AppItem::getVoteScore() const
  393. {
  394. return m_voteScore;
  395. }
  396. int AppItem::getVoteCount() const
  397. {
  398. return m_voteCount;
  399. }
  400. time_t AppItem::getReleaseDate() const
  401. {
  402. return m_releaseDate;
  403. }
  404. const std::string &AppItem::getProductCode() const
  405. {
  406. return m_productCode;
  407. }
  408. bool AppItem::isSleepBusy() const
  409. {
  410. return m_isSleepBusy;
  411. }
  412. void AppItem::setSleepBusy(bool newState)
  413. {
  414. m_isSleepBusy = newState;
  415. }
  416. } // namespace FreeShop