CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/UI/GameScreen.cpp
Views: 1401
// Copyright (c) 2013- PPSSPP Project.12// This program is free software: you can redistribute it and/or modify3// it under the terms of the GNU General Public License as published by4// the Free Software Foundation, version 2.0 or later versions.56// This program is distributed in the hope that it will be useful,7// but WITHOUT ANY WARRANTY; without even the implied warranty of8// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the9// GNU General Public License 2.0 for more details.1011// A copy of the GPL 2.0 should have been included with the program.12// If not, see http://www.gnu.org/licenses/1314// Official git repository and contact information can be found at15// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.1617#include <algorithm>1819#include "ppsspp_config.h"2021#include "Common/Render/DrawBuffer.h"22#include "Common/UI/Context.h"23#include "Common/UI/View.h"24#include "Common/UI/ViewGroup.h"2526#include "Common/Data/Text/I18n.h"27#include "Common/Data/Text/Parsers.h"28#include "Common/Data/Encoding/Utf8.h"29#include "Common/File/FileUtil.h"30#include "Common/StringUtils.h"31#include "Common/System/System.h"32#include "Common/System/OSD.h"33#include "Common/System/Request.h"34#include "Common/System/NativeApp.h"35#include "Core/Config.h"36#include "Core/Reporting.h"37#include "Core/System.h"38#include "Core/Loaders.h"39#include "Core/Util/GameDB.h"40#include "Core/HLE/Plugins.h"41#include "UI/OnScreenDisplay.h"42#include "UI/CwCheatScreen.h"43#include "UI/EmuScreen.h"44#include "UI/GameScreen.h"45#include "UI/GameSettingsScreen.h"46#include "UI/GameInfoCache.h"47#include "UI/MiscScreens.h"48#include "UI/MainScreen.h"49#include "UI/BackgroundAudio.h"50#include "UI/SavedataScreen.h"51#include "Core/Reporting.h"5253GameScreen::GameScreen(const Path &gamePath, bool inGame) : UIDialogScreenWithGameBackground(gamePath), inGame_(inGame) {54g_BackgroundAudio.SetGame(gamePath);55System_PostUIMessage(UIMessage::GAME_SELECTED, gamePath.ToString());56}5758GameScreen::~GameScreen() {59if (CRC32string == "...") {60Reporting::CancelCRC();61}62System_PostUIMessage(UIMessage::GAME_SELECTED, "");63}6465template <typename I> std::string int2hexstr(I w, size_t hex_len = sizeof(I) << 1) {66static const char* digits = "0123456789ABCDEF";67std::string rc(hex_len, '0');68for (size_t i = 0, j = (hex_len - 1) * 4; i < hex_len; ++i, j -= 4)69rc[i] = digits[(w >> j) & 0x0f];70return rc;71}7273void GameScreen::update() {74UIScreen::update();7576// Has the user requested a CRC32?77if (CRC32string == "...") {78// Wait until the CRC32 is ready. It might take time on some devices.79if (Reporting::HasCRC(gamePath_)) {80uint32_t crcvalue = Reporting::RetrieveCRC(gamePath_);81CRC32string = int2hexstr(crcvalue);82tvCRC_->SetVisibility(UI::V_VISIBLE);83tvCRC_->SetText(CRC32string);84if (tvCRCCopy_) {85tvCRCCopy_->SetVisibility(UI::V_VISIBLE);86}87if (btnCalcCRC_) {88btnCalcCRC_->SetVisibility(UI::V_GONE);89}90}91}92}9394void GameScreen::CreateViews() {95std::shared_ptr<GameInfo> info = g_gameInfoCache->GetInfo(NULL, gamePath_, GameInfoFlags::PARAM_SFO | GameInfoFlags::ICON | GameInfoFlags::BG);9697if (info->Ready(GameInfoFlags::PARAM_SFO)) {98saveDirs = info->GetSaveDataDirectories(); // Get's very heavy, let's not do it in update()99}100101auto di = GetI18NCategory(I18NCat::DIALOG);102auto ga = GetI18NCategory(I18NCat::GAME);103104// Information in the top left.105// Back button to the bottom left.106// Scrolling action menu to the right.107using namespace UI;108109Margins actionMenuMargins(0, 100, 15, 0);110111root_ = new LinearLayout(ORIENT_HORIZONTAL);112113ViewGroup *leftColumn = new AnchorLayout(new LinearLayoutParams(1.0f));114root_->Add(leftColumn);115116leftColumn->Add(new Choice(di->T("Back"), "", false, new AnchorLayoutParams(150, WRAP_CONTENT, 10, NONE, NONE, 10)))->OnClick.Handle(this, &GameScreen::OnSwitchBack);117if (info->Ready(GameInfoFlags::PARAM_SFO)) {118ViewGroup *badgeHolder = new LinearLayout(ORIENT_HORIZONTAL, new AnchorLayoutParams(10, 10, 110, NONE));119LinearLayout *mainGameInfo = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(1.0f));120mainGameInfo->SetSpacing(3.0f);121122// Need an explicit size here because homebrew uses screenshots as icons.123badgeHolder->Add(new GameIconView(gamePath_, 2.0f, new LinearLayoutParams(144 * 2, 80 * 2, UI::Margins(0))));124badgeHolder->Add(mainGameInfo);125126leftColumn->Add(badgeHolder);127128LinearLayout *infoLayout = new LinearLayout(ORIENT_VERTICAL, new AnchorLayoutParams(10, 200, NONE, NONE));129leftColumn->Add(infoLayout);130131// TODO: Add non-translated title here if available in gameDB.132tvTitle_ = mainGameInfo->Add(new TextView(info->GetTitle(), ALIGN_LEFT | FLAG_WRAP_TEXT, false, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)));133tvTitle_->SetShadow(true);134tvID_ = mainGameInfo->Add(new TextView("", ALIGN_LEFT | FLAG_WRAP_TEXT, true, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)));135tvID_->SetShadow(true);136tvRegion_ = mainGameInfo->Add(new TextView("", ALIGN_LEFT, true, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)));137tvRegion_->SetShadow(true);138tvGameSize_ = mainGameInfo->Add(new TextView("...", ALIGN_LEFT, true, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)));139tvGameSize_->SetShadow(true);140141// This one doesn't need to be updated.142infoLayout->Add(new TextView(gamePath_.ToVisualString(), ALIGN_LEFT | FLAG_WRAP_TEXT, true, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)))->SetShadow(true);143tvSaveDataSize_ = infoLayout->Add(new TextView("...", ALIGN_LEFT, true, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)));144tvSaveDataSize_->SetShadow(true);145tvInstallDataSize_ = infoLayout->Add(new TextView("", ALIGN_LEFT, true, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)));146tvInstallDataSize_->SetShadow(true);147tvInstallDataSize_->SetVisibility(V_GONE);148tvPlayTime_ = infoLayout->Add(new TextView("", ALIGN_LEFT, true, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)));149tvPlayTime_->SetShadow(true);150tvPlayTime_->SetVisibility(V_GONE);151152LinearLayout *crcHoriz = infoLayout->Add(new LinearLayout(ORIENT_HORIZONTAL));153tvCRC_ = crcHoriz->Add(new TextView("", ALIGN_LEFT, true, new LinearLayoutParams(0.0, G_VCENTER)));154tvCRC_->SetShadow(true);155Visibility crcVisibility = Reporting::HasCRC(gamePath_) ? V_VISIBLE : V_GONE;156tvCRC_->SetVisibility(crcVisibility);157if (System_GetPropertyBool(SYSPROP_HAS_TEXT_CLIPBOARD)) {158tvCRCCopy_ = crcHoriz->Add(new Button(di->T("Copy to clipboard"), new LinearLayoutParams(0.0, G_VCENTER)));159tvCRCCopy_->OnClick.Add([this](UI::EventParams &) {160u32 crc = Reporting::RetrieveCRC(gamePath_);161char buffer[16];162snprintf(buffer, sizeof(buffer), "%08X", crc);163System_CopyStringToClipboard(buffer);164// Success indication. Not worth a translatable string.165g_OSD.Show(OSDType::MESSAGE_SUCCESS, buffer, 1.0f);166return UI::EVENT_DONE;167});168tvCRCCopy_->SetVisibility(crcVisibility);169tvCRCCopy_->SetScale(0.82f);170} else {171tvCRCCopy_ = nullptr;172}173174tvVerified_ = infoLayout->Add(new NoticeView(NoticeLevel::INFO, ga->T("Click \"Calculate CRC\" to verify ISO"), "", new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)));175tvVerified_->SetVisibility(UI::V_GONE);176tvVerified_->SetSquishy(true);177178// Show plugin info, if any. Later might add checkboxes.179auto plugins = HLEPlugins::FindPlugins(info->id, g_Config.sLanguageIni);180if (!plugins.empty()) {181auto sy = GetI18NCategory(I18NCat::SYSTEM);182infoLayout->Add(new TextView(sy->T("Plugins"), ALIGN_LEFT, true));183for (const auto &plugin : plugins) {184infoLayout->Add(new TextView(ApplySafeSubstitutions("* %1", plugin.name), ALIGN_LEFT, true));185}186}187} else {188tvTitle_ = nullptr;189tvID_ = nullptr;190tvGameSize_ = nullptr;191tvSaveDataSize_ = nullptr;192tvInstallDataSize_ = nullptr;193tvRegion_ = nullptr;194tvPlayTime_ = nullptr;195tvCRC_ = nullptr;196tvCRCCopy_ = nullptr;197tvVerified_ = nullptr;198}199200ViewGroup *rightColumn = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(300, FILL_PARENT, actionMenuMargins));201root_->Add(rightColumn);202203LinearLayout *rightColumnItems = new LinearLayout(ORIENT_VERTICAL);204rightColumnItems->SetSpacing(0.0f);205rightColumn->Add(rightColumnItems);206207if (!inGame_) {208rightColumnItems->Add(new Choice(ga->T("Play")))->OnClick.Handle(this, &GameScreen::OnPlay);209}210211btnGameSettings_ = rightColumnItems->Add(new Choice(ga->T("Game Settings")));212btnGameSettings_->OnClick.Handle(this, &GameScreen::OnGameSettings);213214btnDeleteGameConfig_ = rightColumnItems->Add(new Choice(ga->T("Delete Game Config")));215btnDeleteGameConfig_->OnClick.Handle(this, &GameScreen::OnDeleteConfig);216if (inGame_)217btnDeleteGameConfig_->SetEnabled(false);218219btnCreateGameConfig_ = rightColumnItems->Add(new Choice(ga->T("Create Game Config")));220btnCreateGameConfig_->OnClick.Handle(this, &GameScreen::OnCreateConfig);221if (inGame_)222btnCreateGameConfig_->SetEnabled(false);223224btnGameSettings_->SetVisibility(V_GONE);225btnDeleteGameConfig_->SetVisibility(V_GONE);226btnCreateGameConfig_->SetVisibility(V_GONE);227228btnDeleteSaveData_ = new Choice(ga->T("Delete Save Data"));229rightColumnItems->Add(btnDeleteSaveData_)->OnClick.Handle(this, &GameScreen::OnDeleteSaveData);230btnDeleteSaveData_->SetVisibility(V_GONE);231232otherChoices_.clear();233234// Don't want to be able to delete the game while it's running.235Choice *deleteChoice = rightColumnItems->Add(AddOtherChoice(new Choice(ga->T("Delete Game"))));236deleteChoice->OnClick.Handle(this, &GameScreen::OnDeleteGame);237if (inGame_) {238deleteChoice->SetEnabled(false);239}240if (System_GetPropertyBool(SYSPROP_CAN_CREATE_SHORTCUT)) {241rightColumnItems->Add(AddOtherChoice(new Choice(ga->T("Create Shortcut"))))->OnClick.Add([=](UI::EventParams &e) {242std::shared_ptr<GameInfo> info = g_gameInfoCache->GetInfo(NULL, gamePath_, GameInfoFlags::PARAM_SFO);243if (info->Ready(GameInfoFlags::PARAM_SFO)) {244// TODO: Should we block on Ready?245System_CreateGameShortcut(gamePath_, info->GetTitle());246}247return UI::EVENT_DONE;248});249}250251if (isRecentGame(gamePath_)) {252Choice *removeButton = rightColumnItems->Add(AddOtherChoice(new Choice(ga->T("Remove From Recent"))));253removeButton->OnClick.Handle(this, &GameScreen::OnRemoveFromRecent);254if (inGame_) {255removeButton->SetEnabled(false);256}257}258259#if (defined(USING_QT_UI) || PPSSPP_PLATFORM(WINDOWS) || PPSSPP_PLATFORM(MAC)) && !PPSSPP_PLATFORM(UWP)260rightColumnItems->Add(AddOtherChoice(new Choice(ga->T("Show In Folder"))))->OnClick.Handle(this, &GameScreen::OnShowInFolder);261#endif262if (g_Config.bEnableCheats) {263auto pa = GetI18NCategory(I18NCat::PAUSE);264rightColumnItems->Add(AddOtherChoice(new Choice(pa->T("Cheats"))))->OnClick.Handle(this, &GameScreen::OnCwCheat);265}266267btnSetBackground_ = rightColumnItems->Add(new Choice(ga->T("Use UI background")));268btnSetBackground_->OnClick.Handle(this, &GameScreen::OnSetBackground);269btnSetBackground_->SetVisibility(V_GONE);270271bool fileTypeSupportCRC = false;272if (info) {273switch (info->fileType) {274case IdentifiedFileType::PSP_PBP:275case IdentifiedFileType::PSP_PBP_DIRECTORY:276case IdentifiedFileType::PSP_ISO_NP:277case IdentifiedFileType::PSP_ISO:278fileTypeSupportCRC = true;279break;280281default:282break;283}284}285286isHomebrew_ = info && info->region > GAMEREGION_MAX;287if (fileTypeSupportCRC && !isHomebrew_ && !Reporting::HasCRC(gamePath_) ) {288btnCalcCRC_ = rightColumnItems->Add(new ChoiceWithValueDisplay(&CRC32string, ga->T("Calculate CRC"), I18NCat::NONE));289btnCalcCRC_->OnClick.Handle(this, &GameScreen::OnDoCRC32);290} else {291btnCalcCRC_ = nullptr;292}293}294295UI::Choice *GameScreen::AddOtherChoice(UI::Choice *choice) {296otherChoices_.push_back(choice);297// While loading.298choice->SetVisibility(UI::V_GONE);299return choice;300}301302UI::EventReturn GameScreen::OnCreateConfig(UI::EventParams &e) {303std::shared_ptr<GameInfo> info = g_gameInfoCache->GetInfo(nullptr, gamePath_, GameInfoFlags::PARAM_SFO);304if (!info->Ready(GameInfoFlags::PARAM_SFO)) {305return UI::EVENT_SKIPPED;306}307g_Config.createGameConfig(info->id);308g_Config.saveGameConfig(info->id, info->GetTitle());309info->hasConfig = true;310311screenManager()->topScreen()->RecreateViews();312return UI::EVENT_DONE;313}314315void GameScreen::CallbackDeleteConfig(bool yes) {316if (yes) {317std::shared_ptr<GameInfo> info = g_gameInfoCache->GetInfo(nullptr, gamePath_, GameInfoFlags::PARAM_SFO);318if (!info->Ready(GameInfoFlags::PARAM_SFO)) {319return;320}321g_Config.deleteGameConfig(info->id);322info->hasConfig = false;323screenManager()->RecreateAllViews();324}325}326327UI::EventReturn GameScreen::OnDeleteConfig(UI::EventParams &e)328{329auto di = GetI18NCategory(I18NCat::DIALOG);330auto ga = GetI18NCategory(I18NCat::GAME);331screenManager()->push(332new PromptScreen(gamePath_, di->T("DeleteConfirmGameConfig", "Do you really want to delete the settings for this game?"), ga->T("ConfirmDelete"), di->T("Cancel"),333std::bind(&GameScreen::CallbackDeleteConfig, this, std::placeholders::_1)));334335return UI::EVENT_DONE;336}337338ScreenRenderFlags GameScreen::render(ScreenRenderMode mode) {339ScreenRenderFlags flags = UIScreen::render(mode);340341auto ga = GetI18NCategory(I18NCat::GAME);342343Draw::DrawContext *draw = screenManager()->getDrawContext();344345std::shared_ptr<GameInfo> info = g_gameInfoCache->GetInfo(draw, gamePath_, GameInfoFlags::BG | GameInfoFlags::SIZE | GameInfoFlags::UNCOMPRESSED_SIZE);346347if (tvTitle_) {348tvTitle_->SetText(info->GetTitle());349}350351if (info->Ready(GameInfoFlags::SIZE | GameInfoFlags::UNCOMPRESSED_SIZE)) {352char temp[256];353if (tvGameSize_) {354snprintf(temp, sizeof(temp), "%s: %s", ga->T_cstr("Game"), NiceSizeFormat(info->gameSizeOnDisk).c_str());355if (info->gameSizeUncompressed != info->gameSizeOnDisk) {356size_t len = strlen(temp);357snprintf(temp + len, sizeof(temp) - len, " (%s: %s)", ga->T_cstr("Uncompressed"), NiceSizeFormat(info->gameSizeUncompressed).c_str());358}359tvGameSize_->SetText(temp);360}361if (tvSaveDataSize_) {362if (info->saveDataSize > 0) {363snprintf(temp, sizeof(temp), "%s: %s", ga->T_cstr("SaveData"), NiceSizeFormat(info->saveDataSize).c_str());364tvSaveDataSize_->SetText(temp);365} else {366tvSaveDataSize_->SetVisibility(UI::V_GONE);367}368}369if (info->installDataSize > 0 && tvInstallDataSize_) {370snprintf(temp, sizeof(temp), "%s: %1.2f %s", ga->T_cstr("InstallData"), (float) (info->installDataSize) / 1024.f / 1024.f, ga->T_cstr("MB"));371tvInstallDataSize_->SetText(temp);372tvInstallDataSize_->SetVisibility(UI::V_VISIBLE);373}374}375376if (tvRegion_) {377if (info->region >= 0 && info->region < GAMEREGION_MAX && info->region != GAMEREGION_OTHER) {378static const char *regionNames[GAMEREGION_MAX] = {379"Japan",380"USA",381"Europe",382"Hong Kong",383"Asia",384"Korea"385};386tvRegion_->SetText(ga->T(regionNames[info->region]));387} else if (info->region > GAMEREGION_MAX) {388tvRegion_->SetText(ga->T("Homebrew"));389}390}391392if (tvPlayTime_) {393std::string str;394if (g_Config.TimeTracker().GetPlayedTimeString(info->id, &str)) {395tvPlayTime_->SetText(str);396tvPlayTime_->SetVisibility(UI::V_VISIBLE);397}398}399400if (tvCRC_ && Reporting::HasCRC(gamePath_)) {401auto rp = GetI18NCategory(I18NCat::REPORTING);402uint32_t crcVal = Reporting::RetrieveCRC(gamePath_);403std::string crc = StringFromFormat("%08X", crcVal);404tvCRC_->SetText(ReplaceAll(rp->T("FeedbackCRCValue", "Disc CRC: %1"), "%1", crc));405tvCRC_->SetVisibility(UI::V_VISIBLE);406if (tvCRCCopy_) {407tvCRCCopy_->SetVisibility(UI::V_VISIBLE);408}409410// Let's check the CRC in the game database, looking up the ID and also matching the crc.411std::vector<GameDBInfo> dbInfos;412if (tvVerified_ && info->Ready(GameInfoFlags::PARAM_SFO) && g_gameDB.GetGameInfos(info->id_version, &dbInfos)) {413bool found = false;414for (auto &dbInfo : dbInfos) {415if (dbInfo.crc == crcVal) {416found = true;417}418}419if (found) {420tvVerified_->SetText(ga->T("ISO OK according to the ReDump project"));421tvVerified_->SetLevel(NoticeLevel::SUCCESS);422tvVerified_->SetVisibility(UI::V_VISIBLE);423} else {424// Like the other messages below, disabled until we have a database we have confidence in.425// tvVerified_->SetText(ga->T("CRC checksum does not match, bad or modified ISO"));426// tvVerified_->SetLevel(NoticeLevel::ERROR);427tvVerified_->SetVisibility(UI::V_GONE);428}429} else if (tvVerified_) {430// tvVerified_->SetText(ga->T("Game ID unknown - not in the ReDump database"));431// tvVerified_->SetVisibility(UI::V_VISIBLE);432// tvVerified_->SetLevel(NoticeLevel::WARN);433tvVerified_->SetVisibility(UI::V_GONE);434}435} else if (!isHomebrew_) {436GameDBInfo dbInfo;437if (tvVerified_) {438std::vector<GameDBInfo> dbInfos;439if (info->Ready(GameInfoFlags::PARAM_SFO) && !g_gameDB.GetGameInfos(info->id_version, &dbInfos)) {440// tvVerified_->SetText(ga->T("Game ID unknown - not in the ReDump database"));441// tvVerified_->SetVisibility(UI::V_VISIBLE);442// tvVerified_->SetLevel(NoticeLevel::WARN);443} else if (info->Ready(GameInfoFlags::UNCOMPRESSED_SIZE) && info->gameSizeUncompressed != 0) { // don't do this check if info still pending444bool found = false;445for (auto &dbInfo : dbInfos) {446// TODO: Doesn't take CSO/CHD into account.447if (info->gameSizeUncompressed == dbInfo.size) {448found = true;449}450}451if (!found) {452// tvVerified_->SetText(ga->T("File size incorrect, bad or modified ISO"));453// tvVerified_->SetVisibility(UI::V_VISIBLE);454// tvVerified_->SetLevel(NoticeLevel::ERROR);455// INFO_LOG(Log::Loader, "File size %d not matching game DB", (int)info->gameSizeUncompressed);456} else {457tvVerified_->SetText(ga->T("Click \"Calculate CRC\" to verify ISO"));458tvVerified_->SetVisibility(UI::V_VISIBLE);459tvVerified_->SetLevel(NoticeLevel::INFO);460}461}462}463}464465if (tvID_) {466tvID_->SetText(ReplaceAll(info->id_version, "_", " v"));467}468469if (!info->id.empty()) {470btnGameSettings_->SetVisibility(info->hasConfig ? UI::V_VISIBLE : UI::V_GONE);471btnDeleteGameConfig_->SetVisibility(info->hasConfig ? UI::V_VISIBLE : UI::V_GONE);472btnCreateGameConfig_->SetVisibility(info->hasConfig ? UI::V_GONE : UI::V_VISIBLE);473474if (saveDirs.size()) {475btnDeleteSaveData_->SetVisibility(UI::V_VISIBLE);476}477if (info->pic0.texture || info->pic1.texture) {478btnSetBackground_->SetVisibility(UI::V_VISIBLE);479}480}481482if (info->Ready(GameInfoFlags::PARAM_SFO)) {483// At this point, the above buttons won't become visible. We can show these now.484for (UI::Choice *choice : otherChoices_) {485choice->SetVisibility(UI::V_VISIBLE);486}487}488return flags;489}490491UI::EventReturn GameScreen::OnShowInFolder(UI::EventParams &e) {492System_ShowFileInFolder(gamePath_);493return UI::EVENT_DONE;494}495496UI::EventReturn GameScreen::OnCwCheat(UI::EventParams &e) {497screenManager()->push(new CwCheatScreen(gamePath_));498return UI::EVENT_DONE;499}500501UI::EventReturn GameScreen::OnDoCRC32(UI::EventParams& e) {502CRC32string = "...";503Reporting::QueueCRC(gamePath_);504if (btnCalcCRC_) {505btnCalcCRC_->SetEnabled(false);506}507return UI::EVENT_DONE;508}509510511UI::EventReturn GameScreen::OnSwitchBack(UI::EventParams &e) {512TriggerFinish(DR_OK);513return UI::EVENT_DONE;514}515516UI::EventReturn GameScreen::OnPlay(UI::EventParams &e) {517screenManager()->switchScreen(new EmuScreen(gamePath_));518return UI::EVENT_DONE;519}520521UI::EventReturn GameScreen::OnGameSettings(UI::EventParams &e) {522std::shared_ptr<GameInfo> info = g_gameInfoCache->GetInfo(NULL, gamePath_, GameInfoFlags::PARAM_SFO);523if (info && info->Ready(GameInfoFlags::PARAM_SFO)) {524std::string discID = info->GetParamSFO().GetValueString("DISC_ID");525if ((discID.empty() || !info->disc_total) && gamePath_.FilePathContainsNoCase("PSP/GAME/"))526discID = g_paramSFO.GenerateFakeID(gamePath_);527screenManager()->push(new GameSettingsScreen(gamePath_, discID, true));528}529return UI::EVENT_DONE;530}531532UI::EventReturn GameScreen::OnDeleteSaveData(UI::EventParams &e) {533std::shared_ptr<GameInfo> info = g_gameInfoCache->GetInfo(NULL, gamePath_, GameInfoFlags::PARAM_SFO);534if (info) {535// Check that there's any savedata to delete536if (saveDirs.size()) {537auto di = GetI18NCategory(I18NCat::DIALOG);538auto ga = GetI18NCategory(I18NCat::GAME);539screenManager()->push(540new PromptScreen(gamePath_, di->T("DeleteConfirmAll", "Do you really want to delete all\nyour save data for this game?"), ga->T("ConfirmDelete"), di->T("Cancel"),541std::bind(&GameScreen::CallbackDeleteSaveData, this, std::placeholders::_1)));542}543}544RecreateViews();545return UI::EVENT_DONE;546}547548void GameScreen::CallbackDeleteSaveData(bool yes) {549if (yes) {550std::shared_ptr<GameInfo> info = g_gameInfoCache->GetInfo(NULL, gamePath_, GameInfoFlags::PARAM_SFO);551info->DeleteAllSaveData();552info->saveDataSize = 0;553info->installDataSize = 0;554}555}556557UI::EventReturn GameScreen::OnDeleteGame(UI::EventParams &e) {558std::shared_ptr<GameInfo> info = g_gameInfoCache->GetInfo(NULL, gamePath_, GameInfoFlags::PARAM_SFO);559if (info->Ready(GameInfoFlags::PARAM_SFO)) {560auto di = GetI18NCategory(I18NCat::DIALOG);561auto ga = GetI18NCategory(I18NCat::GAME);562std::string prompt;563prompt = di->T("DeleteConfirmGame", "Do you really want to delete this game\nfrom your device? You can't undo this.");564prompt += "\n\n" + gamePath_.ToVisualString(g_Config.memStickDirectory.c_str());565screenManager()->push(566new PromptScreen(gamePath_, prompt, ga->T("ConfirmDelete"), di->T("Cancel"),567std::bind(&GameScreen::CallbackDeleteGame, this, std::placeholders::_1)));568}569return UI::EVENT_DONE;570}571572void GameScreen::CallbackDeleteGame(bool yes) {573if (yes) {574std::shared_ptr<GameInfo> info = g_gameInfoCache->GetInfo(NULL, gamePath_, GameInfoFlags::PARAM_SFO);575info->Delete();576g_gameInfoCache->Clear();577screenManager()->switchScreen(new MainScreen());578}579}580581bool GameScreen::isRecentGame(const Path &gamePath) {582if (g_Config.iMaxRecent <= 0)583return false;584585const std::string resolved = File::ResolvePath(gamePath.ToString());586for (const auto &iso : g_Config.RecentIsos()) {587const std::string recent = File::ResolvePath(iso);588if (resolved == recent)589return true;590}591return false;592}593594UI::EventReturn GameScreen::OnRemoveFromRecent(UI::EventParams &e) {595g_Config.RemoveRecent(gamePath_.ToString());596screenManager()->switchScreen(new MainScreen());597return UI::EVENT_DONE;598}599600class SetBackgroundPopupScreen : public PopupScreen {601public:602SetBackgroundPopupScreen(std::string_view title, const Path &gamePath)603: PopupScreen(title), gamePath_(gamePath) {604timeStart_ = time_now_d();605}606const char *tag() const override { return "SetBackgroundPopup"; }607608protected:609bool FillVertical() const override { return false; }610bool ShowButtons() const override { return false; }611void CreatePopupContents(UI::ViewGroup *parent) override;612void update() override;613614private:615Path gamePath_;616double timeStart_;617double timeDone_ = 0.0;618619enum class Status {620PENDING,621DELAY,622DONE,623};624Status status_ = Status::PENDING;625};626627void SetBackgroundPopupScreen::CreatePopupContents(UI::ViewGroup *parent) {628auto ga = GetI18NCategory(I18NCat::GAME);629parent->Add(new UI::TextView(ga->T("One moment please..."), ALIGN_LEFT | ALIGN_VCENTER, false, new UI::LinearLayoutParams(UI::Margins(10, 0, 10, 10))));630}631632void SetBackgroundPopupScreen::update() {633PopupScreen::update();634635std::shared_ptr<GameInfo> info = g_gameInfoCache->GetInfo(nullptr, gamePath_, GameInfoFlags::BG);636if (status_ == Status::PENDING && info->Ready(GameInfoFlags::BG)) {637GameInfoTex *pic = nullptr;638if (info->pic1.dataLoaded && info->pic1.data.size()) {639pic = &info->pic1;640} else if (info->pic0.dataLoaded && info->pic0.data.size()) {641pic = &info->pic0;642}643644if (pic) {645const Path bgPng = GetSysDirectory(DIRECTORY_SYSTEM) / "background.png";646File::WriteStringToFile(false, pic->data, bgPng);647}648649UIBackgroundShutdown();650651// It's worse if it flickers, stay open for at least 1s.652timeDone_ = timeStart_ + 1.0;653status_ = Status::DELAY;654}655656if (status_ == Status::DELAY && timeDone_ <= time_now_d()) {657TriggerFinish(DR_OK);658status_ = Status::DONE;659}660}661662UI::EventReturn GameScreen::OnSetBackground(UI::EventParams &e) {663auto ga = GetI18NCategory(I18NCat::GAME);664// This popup is used to prevent any race condition:665// g_gameInfoCache may take time to load the data, and a crash could happen if they exit before then.666SetBackgroundPopupScreen *pop = new SetBackgroundPopupScreen(ga->T("Setting Background"), gamePath_);667if (e.v)668pop->SetPopupOrigin(e.v);669screenManager()->push(pop);670return UI::EVENT_DONE;671}672673674