Initial draft of the Homebrew Store. Lets you install a few homebrew.

This commit is contained in:
Henrik Rydgård 2013-11-20 14:42:48 +01:00
parent cd6962497a
commit 2fb2602387
11 changed files with 534 additions and 9 deletions

View File

@ -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)

103
Core/Util/GameManager.cpp Normal file
View File

@ -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);
}

44
Core/Util/GameManager.h Normal file
View File

@ -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<http::Download> curDownload_;
};
extern GameManager g_GameManager;

View File

@ -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;
}

View File

@ -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.

View File

@ -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");

View File

@ -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_;
};

View File

@ -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);

253
UI/Store.cpp Normal file
View File

@ -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<UIScreen>(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<UIScreen>(this, &UIScreen::OnBack);
root_->Add(leftScroll);
LinearLayout *scrollItemView = new LinearLayout(ORIENT_VERTICAL);
leftScroll->Add(scrollItemView);
std::vector<StoreEntry> 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<StoreEntry> StoreScreen::FilterEntries() {
std::vector<StoreEntry> 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<ProductItemView *>(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)";
}
}

102
UI/Store.h Normal file
View File

@ -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<StoreEntry> 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<http::Download> listing_;
std::shared_ptr<http::Download> 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<std::string, StoreCategory> 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<StoreEntry> entries_;
StoreFilter filter_;
std::string lang_;
UI::ViewGroup *productPanel_;
};

View File

@ -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 \