SyncState.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. #include "SyncState.hpp"
  2. #include "../Download.hpp"
  3. #include "../Installer.hpp"
  4. #include "../Util.hpp"
  5. #include "../AssetManager.hpp"
  6. #include "../Config.hpp"
  7. #include "../TitleKeys.hpp"
  8. #include "../FreeShop.hpp"
  9. #include <TweenEngine/Tween.h>
  10. #include <cpp3ds/Window/Window.hpp>
  11. #include <cpp3ds/System/I18n.hpp>
  12. #include <cpp3ds/System/FileSystem.hpp>
  13. #include <cpp3ds/System/Sleep.hpp>
  14. #include <sys/stat.h>
  15. #include <fstream>
  16. #include <cpp3ds/System/Service.hpp>
  17. #include <archive.h>
  18. #include <archive_entry.h>
  19. #include <dirent.h>
  20. #include <cpp3ds/System/FileInputStream.hpp>
  21. namespace {
  22. int copy_data(struct archive *ar, struct archive *aw)
  23. {
  24. int r;
  25. const void *buff;
  26. size_t size;
  27. int64_t offset;
  28. while (1)
  29. {
  30. r = archive_read_data_block(ar, &buff, &size, &offset);
  31. if (r == ARCHIVE_EOF)
  32. return (ARCHIVE_OK);
  33. if (r < ARCHIVE_OK)
  34. return (r);
  35. r = archive_write_data_block(aw, buff, size, offset);
  36. if (r < ARCHIVE_OK)
  37. {
  38. fprintf(stderr, "%s\n", archive_error_string(aw));
  39. return (r);
  40. }
  41. }
  42. }
  43. bool extract(const std::string &filename, const std::string &destDir)
  44. {
  45. struct archive *a;
  46. struct archive *ext;
  47. struct archive_entry *entry;
  48. int r = ARCHIVE_FAILED;
  49. a = archive_read_new();
  50. archive_read_support_format_tar(a);
  51. archive_read_support_compression_gzip(a);
  52. ext = archive_write_disk_new();
  53. if ((r = archive_read_open_filename(a, cpp3ds::FileSystem::getFilePath(filename).c_str(), 32*1024))) {
  54. std::cout << "failure! " << r << std::endl;
  55. return r;
  56. }
  57. while(1)
  58. {
  59. r = archive_read_next_header(a, &entry);
  60. if (r == ARCHIVE_EOF)
  61. break;
  62. if (r < ARCHIVE_OK)
  63. fprintf(stderr, "%s\n", archive_error_string(a));
  64. // TODO: handle these fatal error
  65. std::string path = cpp3ds::FileSystem::getFilePath(destDir + archive_entry_pathname(entry));
  66. if (FreeShop::pathExists(path.c_str(), false))
  67. unlink(path.c_str());
  68. archive_entry_set_pathname(entry, path.c_str());
  69. r = archive_write_header(ext, entry);
  70. if (r < ARCHIVE_OK)
  71. fprintf(stderr, "%s\n", archive_error_string(ext));
  72. else if (archive_entry_size(entry) > 0)
  73. {
  74. r = copy_data(a, ext);
  75. if (r < ARCHIVE_OK)
  76. fprintf(stderr, "%s\n", archive_error_string(ext));
  77. }
  78. r = archive_write_finish_entry(ext);
  79. if (r < ARCHIVE_OK)
  80. fprintf(stderr, "%s\n", archive_error_string(ext));
  81. }
  82. archive_read_close(a);
  83. archive_read_free(a);
  84. archive_write_close(ext);
  85. archive_write_free(ext);
  86. return r == ARCHIVE_EOF;
  87. }
  88. }
  89. namespace FreeShop {
  90. bool g_syncComplete = false;
  91. bool g_browserLoaded = false;
  92. SyncState::SyncState(StateStack& stack, Context& context, StateCallback callback)
  93. : State(stack, context, callback)
  94. , m_threadSync(&SyncState::sync, this)
  95. , m_threadStartupSound(&SyncState::startupSound, this)
  96. {
  97. g_syncComplete = false;
  98. g_browserLoaded = false;
  99. m_soundStartup.setBuffer(AssetManager<cpp3ds::SoundBuffer>::get("sounds/startup.ogg"));
  100. m_soundLoading.setBuffer(AssetManager<cpp3ds::SoundBuffer>::get("sounds/loading.ogg"));
  101. m_soundLoading.setLoop(true);
  102. m_textStatus.setCharacterSize(14);
  103. m_textStatus.setFillColor(cpp3ds::Color::Black);
  104. m_textStatus.setOutlineColor(cpp3ds::Color(0, 0, 0, 70));
  105. m_textStatus.setOutlineThickness(2.f);
  106. m_textStatus.setPosition(160.f, 155.f);
  107. m_textStatus.useSystemFont();
  108. TweenEngine::Tween::to(m_textStatus, util3ds::TweenText::FILL_COLOR_ALPHA, 0.15f)
  109. .target(180)
  110. .repeatYoyo(-1, 0)
  111. .start(m_tweenManager);
  112. m_threadSync.launch();
  113. m_threadStartupSound.launch();
  114. }
  115. SyncState::~SyncState()
  116. {
  117. m_threadSync.wait();
  118. m_threadStartupSound.wait();
  119. }
  120. void SyncState::renderTopScreen(cpp3ds::Window& window)
  121. {
  122. // Nothing
  123. }
  124. void SyncState::renderBottomScreen(cpp3ds::Window& window)
  125. {
  126. window.draw(m_textStatus);
  127. }
  128. bool SyncState::update(float delta)
  129. {
  130. m_tweenManager.update(delta);
  131. return true;
  132. }
  133. bool SyncState::processEvent(const cpp3ds::Event& event)
  134. {
  135. return true;
  136. }
  137. void SyncState::sync()
  138. {
  139. m_timer.restart();
  140. if (!cpp3ds::Service::isEnabled(cpp3ds::Httpc))
  141. {
  142. while (!cpp3ds::Service::isEnabled(cpp3ds::Httpc) && m_timer.getElapsedTime() < cpp3ds::seconds(30.f))
  143. {
  144. setStatus(_("Waiting for internet connection... %.0fs", 30.f - m_timer.getElapsedTime().asSeconds()));
  145. cpp3ds::sleep(cpp3ds::milliseconds(200));
  146. }
  147. if (!cpp3ds::Service::isEnabled(cpp3ds::Httpc))
  148. {
  149. requestStackClear();
  150. return;
  151. }
  152. }
  153. // If auto-dated, boot into launch newest freeShop
  154. #ifdef NDEBUG
  155. if (updateFreeShop())
  156. {
  157. Config::set(Config::ShowNews, true);
  158. g_requestJump = 0x400000F12EE00;
  159. return;
  160. }
  161. #endif
  162. updateCache();
  163. updateTitleKeys();
  164. if (Config::get(Config::ShowNews).GetBool())
  165. {
  166. setStatus(_("Fetching news for %s ...", FREESHOP_VERSION));
  167. std::string url = _("https://raw.githubusercontent.com/Cruel/freeShop/master/news/%s.txt", FREESHOP_VERSION).toAnsiString();
  168. Download download(url, FREESHOP_DIR "/news.txt");
  169. download.run();
  170. }
  171. setStatus(_("Loading game list..."));
  172. // TODO: Figure out why browse state won't load without this sleep
  173. cpp3ds::sleep(cpp3ds::milliseconds(100));
  174. requestStackPush(States::Browse);
  175. Config::saveToFile();
  176. while (m_timer.getElapsedTime() < cpp3ds::seconds(7.f))
  177. cpp3ds::sleep(cpp3ds::milliseconds(50));
  178. g_syncComplete = true;
  179. }
  180. bool SyncState::updateFreeShop()
  181. {
  182. if (!Config::get(Config::AutoUpdate).GetBool() && !Config::get(Config::TriggerUpdateFlag).GetBool())
  183. return false;
  184. setStatus(_("Looking for freeShop update..."));
  185. const char *url = "https://api.github.com/repos/Cruel/freeShop/releases/latest";
  186. const char *latestJsonFilename = FREESHOP_DIR "/tmp/latest.json";
  187. Download download(url, latestJsonFilename);
  188. download.run();
  189. cpp3ds::FileInputStream jsonFile;
  190. if (jsonFile.open(latestJsonFilename))
  191. {
  192. std::string json;
  193. rapidjson::Document doc;
  194. int size = jsonFile.getSize();
  195. json.resize(size);
  196. jsonFile.read(&json[0], size);
  197. if (json.empty())
  198. return false;
  199. doc.Parse(json.c_str());
  200. if (!doc.HasMember("tag_name"))
  201. return false;
  202. std::string tag = doc["tag_name"].GetString();
  203. Config::set(Config::LastUpdatedTime, static_cast<int>(time(nullptr)));
  204. Config::saveToFile();
  205. if (!tag.empty() && tag.compare(FREESHOP_VERSION) != 0)
  206. {
  207. #ifndef EMULATION
  208. Result ret;
  209. Handle cia;
  210. bool suceeded = false;
  211. size_t total = 0;
  212. std::string freeShopFile = FREESHOP_DIR "/tmp/freeShop.cia";
  213. std::string freeShopUrl = _("https://github.com/Cruel/freeShop/releases/download/%s/freeShop-%s.cia", tag.c_str(), tag.c_str());
  214. setStatus(_("Installing freeShop %s ...", tag.c_str()));
  215. Download freeShopDownload(freeShopUrl, freeShopFile);
  216. AM_QueryAvailableExternalTitleDatabase(nullptr);
  217. freeShopDownload.run();
  218. cpp3ds::Int64 bytesRead;
  219. cpp3ds::FileInputStream f;
  220. f.open(freeShopFile);
  221. char *buf = new char[128*1024];
  222. AM_StartCiaInstall(MEDIATYPE_SD, &cia);
  223. while (bytesRead = f.read(buf, 128*1024))
  224. {
  225. if (R_FAILED(ret = FSFILE_Write(cia, nullptr, total, buf, bytesRead, 0)))
  226. break;
  227. total += bytesRead;
  228. }
  229. delete[] buf;
  230. if (R_FAILED(ret))
  231. setStatus(_("Failed to install update: 0x%08lX", ret));
  232. suceeded = R_SUCCEEDED(ret = AM_FinishCiaInstall(cia));
  233. if (!suceeded)
  234. setStatus(_("Failed to install update: 0x%08lX", ret));
  235. return suceeded;
  236. #endif
  237. }
  238. }
  239. return false;
  240. }
  241. bool SyncState::updateCache()
  242. {
  243. // Fetch cache if it doesn't exist, regardless of settings
  244. bool cacheExists = pathExists(FREESHOP_DIR "/cache/data.json");
  245. if (cacheExists && Config::get(Config::CacheVersion).GetStringLength() > 0)
  246. if (!Config::get(Config::AutoUpdate).GetBool() && !Config::get(Config::TriggerUpdateFlag).GetBool())
  247. return false;
  248. // In case this flag triggered the update, reset it
  249. Config::set(Config::TriggerUpdateFlag, false);
  250. setStatus(_("Checking latest cache..."));
  251. const char *url = "https://api.github.com/repos/Repo3DS/shop-cache/releases/latest";
  252. const char *latestJsonFilename = FREESHOP_DIR "/tmp/latest.json";
  253. Download cache(url, latestJsonFilename);
  254. cache.run();
  255. cpp3ds::FileInputStream jsonFile;
  256. if (jsonFile.open(latestJsonFilename))
  257. {
  258. std::string json;
  259. rapidjson::Document doc;
  260. int size = jsonFile.getSize();
  261. json.resize(size);
  262. jsonFile.read(&json[0], size);
  263. doc.Parse(json.c_str());
  264. std::string tag = doc["tag_name"].GetString();
  265. if (!cacheExists || (!tag.empty() && tag.compare(Config::get(Config::CacheVersion).GetString()) != 0))
  266. {
  267. std::string cacheFile = FREESHOP_DIR "/tmp/cache.tar.gz";
  268. #ifdef _3DS
  269. std::string cacheUrl = _("https://github.com/Repo3DS/shop-cache/releases/download/%s/cache-%s-etc1.tar.gz", tag.c_str(), tag.c_str());
  270. #else
  271. std::string cacheUrl = _("https://github.com/Repo3DS/shop-cache/releases/download/%s/cache-%s-jpg.tar.gz", tag.c_str(), tag.c_str());
  272. #endif
  273. setStatus(_("Downloading latest cache: %s...", tag.c_str()));
  274. Download cacheDownload(cacheUrl, cacheFile);
  275. cacheDownload.run();
  276. setStatus(_("Extracting latest cache..."));
  277. if (extract(cacheFile, FREESHOP_DIR "/cache/"))
  278. {
  279. Config::set(Config::CacheVersion, tag.c_str());
  280. Config::saveToFile();
  281. return true;
  282. }
  283. }
  284. }
  285. return false;
  286. }
  287. bool SyncState::updateTitleKeys()
  288. {
  289. auto urls = Config::get(Config::KeyURLs).GetArray();
  290. if (!Config::get(Config::DownloadTitleKeys).GetBool() || urls.Empty())
  291. return false;
  292. setStatus(_("Downloading title keys..."));
  293. const char *url = urls[0].GetString();
  294. std::string tmpFilename = cpp3ds::FileSystem::getFilePath(FREESHOP_DIR "/tmp/keys.bin");
  295. std::string keysFilename = cpp3ds::FileSystem::getFilePath(FREESHOP_DIR "/keys/download.bin");
  296. Download download(url, tmpFilename);
  297. download.run();
  298. if (!TitleKeys::isValidFile(tmpFilename))
  299. return false;
  300. if (pathExists(keysFilename.c_str()))
  301. unlink(keysFilename.c_str());
  302. rename(tmpFilename.c_str(), keysFilename.c_str());
  303. return true;
  304. }
  305. void SyncState::setStatus(const std::string &message)
  306. {
  307. m_textStatus.setString(message);
  308. cpp3ds::FloatRect rect = m_textStatus.getLocalBounds();
  309. m_textStatus.setOrigin(rect.width / 2.f, rect.height / 2.f);
  310. }
  311. void SyncState::startupSound()
  312. {
  313. cpp3ds::Clock clock;
  314. while (clock.getElapsedTime() < cpp3ds::seconds(3.f))
  315. cpp3ds::sleep(cpp3ds::milliseconds(50));
  316. m_soundStartup.play(0);
  317. // while (clock.getElapsedTime() < cpp3ds::seconds(7.f))
  318. // cpp3ds::sleep(cpp3ds::milliseconds(50));
  319. // m_soundLoading.play(0);
  320. }
  321. } // namespace FreeShop