From 2fb26023872e208e0ae772255d60d159dab65a35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 20 Nov 2013 14:42:48 +0100 Subject: [PATCH] Initial draft of the Homebrew Store. Lets you install a few homebrew. --- CMakeLists.txt | 13 +- Core/Util/GameManager.cpp | 103 ++++++++++++++++ Core/Util/GameManager.h | 44 +++++++ UI/GameInfoCache.cpp | 6 +- UI/GameInfoCache.h | 10 +- UI/MainScreen.cpp | 7 ++ UI/MainScreen.h | 1 + UI/NativeApp.cpp | 3 + UI/Store.cpp | 253 ++++++++++++++++++++++++++++++++++++++ UI/Store.h | 102 +++++++++++++++ android/jni/Android.mk | 1 + 11 files changed, 534 insertions(+), 9 deletions(-) create mode 100644 Core/Util/GameManager.cpp create mode 100644 Core/Util/GameManager.h create mode 100644 UI/Store.cpp create mode 100644 UI/Store.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 9980fb9725..9825926c81 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -371,7 +371,7 @@ add_library(stb_vorbis STATIC native/ext/stb_vorbis/stb_vorbis.h) include_directories(native/ext/stb_vorbis) -if(ANDROID) +# if(ANDROID) add_library(libzip STATIC native/ext/libzip/zip.h native/ext/libzip/mkstemp.c @@ -431,7 +431,7 @@ if(ANDROID) target_link_libraries(libzip zlib) include_directories(native/ext/libzip) set(LIBZIP libzip zlib) -endif() +# endif() set(nativeExtra) set(nativeExtraLibs) @@ -532,6 +532,10 @@ add_library(native STATIC native/base/timeutil.h native/data/compression.cpp native/data/compression.h + native/ext/vjson/json.cpp + native/ext/vjson/json.h + native/ext/vjson/block_allocator.cpp + native/ext/vjson/block_allocator.h native/file/chunk_file.cpp native/file/chunk_file.h native/file/dialog.cpp @@ -655,7 +659,7 @@ add_library(native STATIC native/ext/jpge/jpge.cpp native/ext/jpge/jpge.h) include_directories(native) -target_link_libraries(native ${LIBZIP} rg_etc1 vjson stb_image stb_vorbis snappy ${GLEW_LIBRARIES}) +target_link_libraries(native ${LIBZIP} rg_etc1 vjson stb_image stb_vorbis snappy libzip ${GLEW_LIBRARIES}) if(ANDROID) target_link_libraries(native log) @@ -972,6 +976,8 @@ add_library(${CoreLibName} ${CoreLinkType} Core/SaveState.h Core/System.cpp Core/System.h + Core/Util/GameManager.cpp + Core/Util/GameManager.h Core/Util/BlockAllocator.cpp Core/Util/BlockAllocator.h Core/Util/PPGeDraw.cpp @@ -1152,6 +1158,7 @@ set(NativeAppSource UI/UIShader.cpp UI/OnScreenDisplay.cpp UI/ControlMappingScreen.cpp + UI/Store.cpp UI/CwCheatScreen.cpp UI/ui_atlas.cpp) if(ANDROID AND ARM) diff --git a/Core/Util/GameManager.cpp b/Core/Util/GameManager.cpp new file mode 100644 index 0000000000..edc8bb8f54 --- /dev/null +++ b/Core/Util/GameManager.cpp @@ -0,0 +1,103 @@ +// Copyright (c) 2013- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include "file/file_util.h" +#include "native/ext/libzip/zip.h" + +#include "Common/Log.h" +#include "Common/FileUtil.h" +#include "Core/System.h" +#include "Core/Util/GameManager.h" + +GameManager g_GameManager; + +bool GameManager::IsGameInstalled(std::string name) { + return false; +} + +bool GameManager::DownloadAndInstall(std::string storeZipUrl) { + if (curDownload_.get() != 0) { + ERROR_LOG(HLE, "Can only process one download at a time"); + return false; + } + + // TODO: Android-compatible temp file names + curDownload_ = downloader_.StartDownload(storeZipUrl, "/tmp/ppsspp.dl"); + return true; +} + +void GameManager::Uninstall(std::string name) { + +} + +void GameManager::Update() { + if (curDownload_.get() && curDownload_->Done()) { + // Install the game! + std::string zipName = curDownload_->outfile(); + InstallGame(zipName); + // Doesn't matter if the install succeeds or not, we delete the temp file to not squander space. + // TODO: Handle disk full? + deleteFile(zipName.c_str()); + curDownload_.reset(); + } +} + +void GameManager::InstallGame(std::string zipfile) { + std::string pspGame = GetSysDirectory(DIRECTORY_GAME); + INFO_LOG(HLE, "Installing %s into %s", zipfile.c_str(), pspGame.c_str()); + + int error; + struct zip *z = zip_open(zipfile.c_str(), 0, &error); + if (!z) { + ERROR_LOG(HLE, "Failed to open ZIP file %s, error code=%i", zipfile.c_str(), error); + return; + } + + int numFiles = zip_get_num_files(z); + + for (int i = 0; i < numFiles; i++) { + const char *fn = zip_get_name(z, i, 0); + if (strstr(fn, "/") != 0) { + INFO_LOG(HLE, "File: %i: %s", i, fn); + struct zip_stat zstat; + int x = zip_stat_index(z, i, 0, &zstat); + size_t size = zstat.size; + u8 *buffer = new u8[size]; + zip_file *zf = zip_fopen_index(z, i, 0); + zip_fread(zf, buffer, size); + zip_fclose(zf); + + std::string outFilename = pspGame + fn; + bool isDir = outFilename.back() == '/'; + if (isDir) { + File::CreateFullPath(outFilename.c_str()); + } else { + INFO_LOG(HLE, "Writing %i bytes to %s", (int)size, outFilename.c_str()); + FILE *f = fopen(outFilename.c_str(), "wb"); + if (f) { + fwrite(buffer, 1, size, f); + fclose(f); + } else { + ERROR_LOG(HLE, "Failed to open file for writing"); + } + delete [] buffer; + } + } + } + + zip_close(z); +} diff --git a/Core/Util/GameManager.h b/Core/Util/GameManager.h new file mode 100644 index 0000000000..18f87b1875 --- /dev/null +++ b/Core/Util/GameManager.h @@ -0,0 +1,44 @@ +// Copyright (c) 2013- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + + +// Manages the PSP/GAME directory contents. +// +// Not concerned with full ISOs. + +#include "net/http_client.h" + +class GameManager { +public: + bool IsGameInstalled(std::string name); + + // This starts off a background process. + bool DownloadAndInstall(std::string storeZipUrl); + void Uninstall(std::string name); + + // Call from time to time to check on completed downloads from the + // main UI thread. + void Update(); + +private: + void InstallGame(std::string zipfile); + + http::Downloader downloader_; + std::shared_ptr curDownload_; +}; + +extern GameManager g_GameManager; diff --git a/UI/GameInfoCache.cpp b/UI/GameInfoCache.cpp index 2ff3adc58a..f28e542e65 100644 --- a/UI/GameInfoCache.cpp +++ b/UI/GameInfoCache.cpp @@ -439,7 +439,7 @@ void GameInfoCache::Add(const std::string &key, GameInfo *info_) { // This may run off-main-thread and we thus can't use the global // pspFileSystem (well, we could with synchronization but there might not // even be a game running). -GameInfo *GameInfoCache::GetInfo(const std::string &gamePath, bool wantBG) { +GameInfo *GameInfoCache::GetInfo(const std::string &gamePath, bool wantBG, bool synchronous) { auto iter = info_.find(gamePath); if (iter != info_.end()) { GameInfo *info = iter->second; @@ -501,6 +501,10 @@ again: GameInfoWorkItem *item = new GameInfoWorkItem(gamePath, info); gameInfoWQ_->Add(item); + if (synchronous) { + gameInfoWQ_->Wait(item); + } + info_[gamePath] = info; return info; } diff --git a/UI/GameInfoCache.h b/UI/GameInfoCache.h index db0462f1ea..84fa511e88 100644 --- a/UI/GameInfoCache.h +++ b/UI/GameInfoCache.h @@ -27,15 +27,15 @@ #include "Core/ELF/ParamSFO.h" #include "Core/Loaders.h" - // A GameInfo holds information about a game, and also lets you do things that the VSH // does on the PSP, namely checking for and deleting savedata, and similar things. +// Only cares about games that are installed on the current device. class GameInfo { public: - GameInfo() + GameInfo() : fileType(FILETYPE_UNKNOWN), paramSFOLoaded(false), iconTexture(NULL), pic0Texture(NULL), pic1Texture(NULL), - wantBG(false), gameSize(0), saveDataSize(0), installDataSize(0), disc_total(0), disc_number(0) {} + wantBG(false), gameSize(0), saveDataSize(0), installDataSize(0), disc_total(0), disc_number(0) {} bool DeleteGame(); // Better be sure what you're doing when calling this. bool DeleteAllSaveData(); @@ -64,7 +64,7 @@ public: IdentifiedFileType fileType; ParamSFOData paramSFO; bool paramSFOLoaded; - + // Pre read the data, create a texture the next time (GL thread..) std::string iconTextureData; Texture *iconTexture; @@ -102,7 +102,7 @@ public: // but filled in later asynchronously in the background. So keep calling this, // redrawing the UI often. Only set wantBG if you really want it because // it's big. bgTextures may be discarded over time as well. - GameInfo *GetInfo(const std::string &gamePath, bool wantBG); + GameInfo *GetInfo(const std::string &gamePath, bool wantBG, bool synchronous = false); void Decimate(); // Deletes old info. void FlushBGs(); // Gets rid of all BG textures. diff --git a/UI/MainScreen.cpp b/UI/MainScreen.cpp index 6390d0a25e..e395ea152c 100644 --- a/UI/MainScreen.cpp +++ b/UI/MainScreen.cpp @@ -40,6 +40,7 @@ #include "UI/CwCheatScreen.h" #include "UI/MiscScreens.h" #include "UI/ControlMappingScreen.h" +#include "UI/Store.h" #include "UI/ui_atlas.h" #include "Core/Config.h" #include "GPU/GPUInterface.h" @@ -501,6 +502,7 @@ void MainScreen::CreateViews() { #endif rightColumnItems->Add(new Choice(m->T("Game Settings", "Settings")))->OnClick.Handle(this, &MainScreen::OnGameSettings); rightColumnItems->Add(new Choice(m->T("Credits")))->OnClick.Handle(this, &MainScreen::OnCredits); + rightColumnItems->Add(new Choice(m->T("Homebrew Store")))->OnClick.Handle(this, &MainScreen::OnHomebrewStore); #ifndef __SYMBIAN32__ rightColumnItems->Add(new Choice(m->T("www.ppsspp.org")))->OnClick.Handle(this, &MainScreen::OnPPSSPPOrg); #endif @@ -648,6 +650,11 @@ UI::EventReturn MainScreen::OnCredits(UI::EventParams &e) { return UI::EVENT_DONE; } +UI::EventReturn MainScreen::OnHomebrewStore(UI::EventParams &e) { + screenManager()->push(new StoreScreen()); + return UI::EVENT_DONE; +} + UI::EventReturn MainScreen::OnSupport(UI::EventParams &e) { #ifdef ANDROID LaunchBrowser("market://details?id=org.ppsspp.ppssppgold"); diff --git a/UI/MainScreen.h b/UI/MainScreen.h index 2b31326f4c..f46657f29d 100644 --- a/UI/MainScreen.h +++ b/UI/MainScreen.h @@ -51,6 +51,7 @@ private: UI::EventReturn OnExit(UI::EventParams &e); UI::EventReturn OnDownloadUpgrade(UI::EventParams &e); UI::EventReturn OnDismissUpgrade(UI::EventParams &e); + UI::EventReturn OnHomebrewStore(UI::EventParams &e); UI::LinearLayout *upgradeBar_; }; diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index 1110614194..e3282c41f3 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -61,6 +61,7 @@ #include "Core/HLE/sceCtrl.h" #include "Core/Host.h" #include "Core/SaveState.h" +#include "Core/Util/GameManager.h" #include "Common/MemArena.h" #include "ui_atlas.h" @@ -566,6 +567,8 @@ void TakeScreenshot() { } void NativeRender() { + g_GameManager.Update(); + glstate.depthWrite.set(GL_TRUE); glstate.colorMask.set(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); diff --git a/UI/Store.cpp b/UI/Store.cpp new file mode 100644 index 0000000000..e351e72de9 --- /dev/null +++ b/UI/Store.cpp @@ -0,0 +1,253 @@ +#include "native/ext/vjson/json.h" + +#include "i18n/i18n.h" +#include "ui/ui_context.h" +#include "ui/viewgroup.h" +#include "gfx_es2/draw_buffer.h" + +#include "Common/Log.h" +#include "Core/Config.h" +#include "UI/Store.h" + +const std::string storeBaseUrl = "http://store.ppsspp.org/"; + +// This is the entry in a list. Does not have install buttons and so on. +class ProductItemView : public UI::Choice { +public: + ProductItemView(const StoreEntry &entry, UI::LayoutParams *layoutParams = 0) + : entry_(entry), UI::Choice(entry.name, layoutParams) {} + + virtual void GetContentDimensions(const UIContext &dc, float &w, float &h) const { + w = 300; + h = 164; + } + virtual void Update(const InputState &input_state); + virtual void Draw(UIContext &dc); + + StoreEntry GetEntry() const { return entry_; } + +private: + const StoreEntry &entry_; +}; + +void ProductItemView::Draw(UIContext &dc) { + UI::Choice::Draw(dc); + // dc.DrawText(entry_.name.c_str(), bounds_.centerX(), bounds_.centerY(), 0xFFFFFFFF, ALIGN_CENTER); +} + +void ProductItemView::Update(const InputState &input_state) { + View::Update(input_state); +} + +// This is a "details" view of a game. Let's you install it. +class ProductView : public UI::LinearLayout { +public: + ProductView(const StoreEntry &entry) : LinearLayout(UI::ORIENT_VERTICAL), entry_(entry) { + CreateViews(); + } + + virtual void Update(const InputState &input_state); + +private: + void CreateViews(); + UI::EventReturn OnInstall(UI::EventParams &e); + UI::EventReturn OnUninstall(UI::EventParams &e); + + StoreEntry entry_; +}; + +void ProductView::CreateViews() { + using namespace UI; + Clear(); + + Add(new TextView(entry_.name)); + Add(new TextView(entry_.author)); + + I18NCategory *s = GetI18NCategory("Store"); + if (!g_GameManager.IsGameInstalled(entry_.file)) { + Add(new Button(s->T("Install")))->OnClick.Handle(this, &ProductView::OnInstall); + } else { + Add(new Button(s->T("Uninstall")))->OnClick.Handle(this, &ProductView::OnUninstall); + } + // Add star rating, comments etc? + Add(new TextView(entry_.description)); + + float size = entry_.size / (1024.f * 1024.f); + char temp[256]; + sprintf(temp, "%s: %f %s", s->T("Size"), size, s->T("MB")); + + Add(new TextView(temp)); +} + +void ProductView::Update(const InputState &input_state) { + View::Update(input_state); + // TODO: Update download progress bar, etc. +} + +UI::EventReturn ProductView::OnInstall(UI::EventParams &e) { + std::string zipUrl = storeBaseUrl + "files/" + entry_.file + ".zip"; + INFO_LOG(HLE, "Triggering install of %s", zipUrl.c_str()); + g_GameManager.DownloadAndInstall(zipUrl); + return UI::EVENT_DONE; +} + +UI::EventReturn ProductView::OnUninstall(UI::EventParams &e) { + g_GameManager.Uninstall(entry_.file); + return UI::EVENT_DONE; +} + + +StoreScreen::StoreScreen() : loading_(true), connectionError_(false) { + StoreFilter noFilter; + SetFilter(noFilter); + lang_ = g_Config.sLanguageIni; + loading_ = true; + + std::string indexPath = storeBaseUrl + "index.json"; + + listing_ = downloader_.StartDownload(indexPath, ""); +} + +StoreScreen::~StoreScreen() { + downloader_.CancelAll(); +} + +// Handle async download tasks +void StoreScreen::update(InputState &input) { + UIDialogScreenWithBackground::update(input); + + downloader_.Update(); + + if (listing_.get() != 0 && listing_->Done()) { + if (listing_->ResultCode() == 200) { + std::string listingJson; + listing_->buffer().TakeAll(&listingJson); + printf("%s\n", listingJson.c_str()); + loading_ = false; + + ParseListing(listingJson); + RecreateViews(); + } else { + // Failed to contact store. Don't do anything. + connectionError_ = true; + RecreateViews(); + } + + // Forget the listing. + listing_.reset(); + } +} + +void StoreScreen::ParseListing(std::string json) { + JsonReader reader(json.c_str(), json.size()); + if (!reader.ok()) { + ELOG("Error parsing JSON from store"); + connectionError_ = true; + RecreateViews(); + return; + } + json_value *root = reader.root(); + const json_value *entries = root->getArray("entries"); + if (entries) { + entries_.clear(); + const json_value *game = entries->first_child; + while (game) { + StoreEntry e; + e.type = ENTRY_PBPZIP; + e.name = GetTranslatedString(game, "name"); + e.description = GetTranslatedString(game, "description", ""); + e.author = game->getString("author", "?"); + e.size = game->getInt("size"); + const char *file = game->getString("file", 0); + if (!file) + continue; + e.file = file; + entries_.push_back(e); + game = game->next_sibling; + ILOG("%s", e.name.c_str()); + } + } +} + +void StoreScreen::CreateViews() { + using namespace UI; + if (connectionError_ || loading_) { + root_ = new LinearLayout(ORIENT_VERTICAL); + root_->Add(new TextView(loading_ ? "Loading.." : "Connection Error")); + root_->Add(new Button("Retry"))->OnClick.Handle(this, &StoreScreen::OnRetry); + root_->Add(new Button("Back"))->OnClick.Handle(this, &UIScreen::OnBack); + } else { + root_ = new LinearLayout(ORIENT_HORIZONTAL); + ScrollView *leftScroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(0.5f)); + root_->Add(new Button("Back"))->OnClick.Handle(this, &UIScreen::OnBack); + root_->Add(leftScroll); + LinearLayout *scrollItemView = new LinearLayout(ORIENT_VERTICAL); + leftScroll->Add(scrollItemView); + std::vector entries = FilterEntries(); + for (size_t i = 0; i < entries.size(); i++) { + ILOG("Adding %s", entries[i].name.c_str()); + scrollItemView->Add(new ProductItemView(entries_[i]))->OnClick.Handle(this, &StoreScreen::OnGameSelected); + } + + // TODO: Similar apps, etc etc + productPanel_ = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(0.5f)); + root_->Add(productPanel_); + } +} + +std::vector StoreScreen::FilterEntries() { + std::vector filtered; + for (size_t i = 0; i < entries_.size(); i++) { + // TODO: Actually filter by category etc. + filtered.push_back(entries_[i]); + } + return filtered; +} + +UI::EventReturn StoreScreen::OnGameSelected(UI::EventParams &e) { + ProductItemView *item = static_cast(e.v); + if (!item) + return UI::EVENT_DONE; + + productPanel_->Clear(); + productPanel_->Add(new ProductView(item->GetEntry())); + return UI::EVENT_DONE; +} + +void StoreScreen::SetFilter(const StoreFilter &filter) { + filter_ = filter; + RecreateViews(); +} + +UI::EventReturn StoreScreen::OnRetry(UI::EventParams &e) { + SetFilter(filter_); + return UI::EVENT_DONE; +} + +std::string StoreScreen::GetStoreJsonURL(std::string storePath) const { + std::string path = storeBaseUrl + storePath; + if (path.back() != '/') + path += '/'; + path += "index.json"; + return path; +} + +std::string StoreScreen::GetTranslatedString(const json_value *json, std::string key, const char *fallback) const { + ILOG("getTranslatedString %s", key.c_str()); + const json_value *dict = json->getDict("en_US"); + if (dict && json->hasChild(lang_.c_str(), JSON_OBJECT)) { + if (json->getDict(lang_.c_str())->hasChild(key.c_str(), JSON_STRING)) { + dict = json->getDict(lang_.c_str()); + } + } + const char *str = 0; + if (dict) { + str = dict->getString(key.c_str()); + } + if (str) { + return std::string(str); + } else { + return fallback ? fallback : "(error)"; + } +} + diff --git a/UI/Store.h b/UI/Store.h new file mode 100644 index 0000000000..a59efcfca5 --- /dev/null +++ b/UI/Store.h @@ -0,0 +1,102 @@ +// Copyright (c) 2013- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#pragma once + +#include "base/functional.h" +#include "ui/ui_screen.h" +#include "ui/viewgroup.h" +#include "net/http_client.h" + +#include "Core/Util/GameManager.h" +#include "UI/MiscScreens.h" + +// Game screen: Allows you to start a game, delete saves, delete the game, +// set game specific settings, etc. +// Uses GameInfoCache heavily to implement the functionality. + +struct json_value; + +enum EntryType { + ENTRY_PBPZIP, + ENTRY_ISO, +}; + +struct StoreCategory { + std::string name; +}; + +struct StoreEntry { + EntryType type; + std::string name; + std::string description; + std::string author; + std::string iconURL; + std::string file; // This is the folder name of the installed one too, and hence a "unique-ish" identifier. + std::string category; + u64 size; +}; + +struct StoreFilter { + std::string categoryId; +}; + +class StoreScreen : public UIDialogScreenWithBackground { +public: + StoreScreen(); + ~StoreScreen(); + + virtual void update(InputState &input); + +protected: + virtual void CreateViews(); + UI::EventReturn OnGameSelected(UI::EventParams &e); + UI::EventReturn OnCategorySelected(UI::EventParams &e); + UI::EventReturn OnRetry(UI::EventParams &e); + +private: + void SetFilter(const StoreFilter &filter); + void ParseListing(std::string json); + std::vector FilterEntries(); + + std::string GetStoreJsonURL(std::string storePath) const; + std::string GetTranslatedString(const json_value *json, std::string key, const char *fallback = 0) const; + + http::Downloader downloader_; + + std::shared_ptr listing_; + std::shared_ptr image_; + + // TODO: Replace with a PathBrowser or similar. Though that one only supports + // local filesystems at the moment. + std::string storePath_; + + bool loading_; + bool connectionError_; + + std::map categories_; + + // We download the whole store in one JSON request. Not super scalable but works fine + // for now. entries_ contains all the products in the store. + std::vector entries_; + + StoreFilter filter_; + std::string lang_; + + UI::ViewGroup *productPanel_; +}; + diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 2e159c3ead..d460d52d38 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -254,6 +254,7 @@ EXEC_AND_LIB_FILES := \ $(SRC)/Core/FileSystems/tlzrc.cpp \ $(SRC)/Core/MIPS/JitCommon/JitCommon.cpp \ $(SRC)/Core/MIPS/JitCommon/JitBlockCache.cpp \ + $(SRC)/Core/Util/GameManager.cpp \ $(SRC)/Core/Util/BlockAllocator.cpp \ $(SRC)/Core/Util/ppge_atlas.cpp \ $(SRC)/Core/Util/PPGeDraw.cpp \