ppsspp/UI/DriverManagerScreen.cpp
Henrik Rydgård 1304d04161 Fix a particular type of race condition in file dialog requests
It seems to be possible for a user to back out of a screen before
receiving the "dialog completed" callback on Android, in which case
things pointed to by the callback might be gone.

In this case, it's better to simply not call the callback, rather than
crashing.

This is accomplished by assigning "Tokens" to screens that cause
requests, and in ~Screen, invalidate any pending requests belonging to
that token.
2024-01-18 12:25:55 +01:00

276 lines
9.0 KiB
C++

#include "Common/File/VFS/ZipFileReader.h"
#include "Common/Data/Format/JSONReader.h"
#include "Common/System/OSD.h"
#include "Common/Log.h"
#include "Common/StringUtils.h"
#include "Core/Config.h"
#include "Core/System.h"
#include "UI/View.h"
#include "UI/DriverManagerScreen.h"
#include "UI/GameSettingsScreen.h" // for triggerrestart
#include "UI/OnScreenDisplay.h"
static Path GetDriverPath() {
if (g_Config.internalDataDirectory.empty()) {
Path curDir = File::GetCurDirectory();
// This is the case when testing on PC
return GetSysDirectory(DIRECTORY_PSP) / "drivers";
} else {
// On Android, this is set to something usable.
return g_Config.internalDataDirectory / "drivers";
}
}
// Example meta.json:
// {
// "schemaVersion": 1,
// "name" : "Turnip driver revision 14",
// "description" : "Compiled from Mesa source.",
// "author" : "KIMCHI",
// "packageVersion" : "1",
// "vendor" : "Mesa",
// "driverVersion" : "Vulkan 1.3.274",
// "minApi" : 27,
// "libraryName" : "vulkan.ad07XX.so"
// }
struct DriverMeta {
int minApi;
std::string name;
std::string description;
std::string vendor;
std::string driverVersion;
bool Read(std::string_view str, std::string *errorStr) {
// Validate the json file. TODO: Be a bit more detailed.
json::JsonReader meta = json::JsonReader((const char *)str.data(), str.size());
if (!meta.ok()) {
*errorStr = "meta.json not valid json";
return false;
}
int schemaVersion = meta.root().getInt("schemaVersion");
if (schemaVersion > 1) {
*errorStr = "unknown schemaVersion in meta.json";
return false;
}
if (!meta.root().getString("name", &name) || name.empty()) {
*errorStr = "missing driver name in json";
return false;
}
meta.root().getString("description", &description);
meta.root().getString("vendor", &vendor);
meta.root().getString("driverVersion", &driverVersion);
minApi = meta.root().getInt("minApi");
return true;
}
};
// Compound view, creating a FileChooserChoice inside.
class DriverChoice : public UI::LinearLayout {
public:
DriverChoice(const std::string &driverName, bool current, UI::LayoutParams *layoutParams = nullptr);
UI::Event OnUse;
UI::Event OnDelete;
std::string name_;
};
static constexpr UI::Size ITEM_HEIGHT = 64.f;
DriverChoice::DriverChoice(const std::string &driverName, bool current, UI::LayoutParams *layoutParams) : UI::LinearLayout(UI::ORIENT_VERTICAL, layoutParams), name_(driverName) {
using namespace UI;
SetSpacing(2.0f);
if (!layoutParams) {
layoutParams_->width = FILL_PARENT;
layoutParams_->height = 220;
}
auto gr = GetI18NCategory(I18NCat::GRAPHICS);
auto di = GetI18NCategory(I18NCat::DIALOG);
// Read the meta data
DriverMeta meta{};
bool isDefault = driverName.empty();
if (isDefault) {
meta.description = gr->T("Default GPU driver");
}
Path metaPath = GetDriverPath() / driverName / "meta.json";
std::string metaJson;
if (File::ReadFileToString(true, metaPath, metaJson)) {
std::string errorStr;
meta.Read(metaJson, &errorStr);
}
Add(new Spacer(12.0));
#if PPSSPP_PLATFORM(ANDROID)
bool usable = isDefault || meta.minApi <= System_GetPropertyInt(SYSPROP_SYSTEMVERSION);
#else
// For testing only
bool usable = isDefault || true;
#endif
Add(new ItemHeader(driverName.empty() ? gr->T("Default GPU driver") : driverName))->SetLarge(true);
if (current) {
Add(new NoticeView(NoticeLevel::SUCCESS, gr->T("Current GPU driver"), ""));
}
auto horizBar = Add(new UI::LinearLayout(UI::ORIENT_HORIZONTAL));
std::string desc = meta.description;
if (!desc.empty()) desc += "\n";
if (!isDefault)
desc += meta.vendor + ": " + meta.driverVersion;
horizBar->Add(new TextView(desc));
if (!current && !isDefault) {
horizBar->Add(new Choice(ImageID("I_TRASHCAN"), new LinearLayoutParams(ITEM_HEIGHT, ITEM_HEIGHT)))->OnClick.Add([=](UI::EventParams &) {
UI::EventParams e{};
e.s = name_;
OnDelete.Trigger(e);
return UI::EVENT_DONE;
});
}
if (usable) {
if (!current) {
Add(new Choice(di->T("Select")))->OnClick.Add([=](UI::EventParams &) {
UI::EventParams e{};
e.s = name_;
OnUse.Trigger(e);
return UI::EVENT_DONE;
});
}
} else {
Add(new NoticeView(NoticeLevel::WARN, ApplySafeSubstitutions(gr->T("Driver requires Android API version %1, current is %2"), meta.minApi, System_GetPropertyInt(SYSPROP_SYSTEMVERSION)),""));
}
}
DriverManagerScreen::DriverManagerScreen(const Path & gamePath) : TabbedUIDialogScreenWithGameBackground(gamePath) {}
void DriverManagerScreen::CreateTabs() {
using namespace UI;
auto gr = GetI18NCategory(I18NCat::GRAPHICS);
LinearLayout *drivers = AddTab("DriverManagerDrivers", gr->T("Drivers"));
CreateDriverTab(drivers);
}
void DriverManagerScreen::CreateDriverTab(UI::ViewGroup *drivers) {
using namespace UI;
auto di = GetI18NCategory(I18NCat::DIALOG);
auto gr = GetI18NCategory(I18NCat::GRAPHICS);
drivers->Add(new ItemHeader(gr->T("AdrenoTools driver manager")));
auto customDriverInstallChoice = drivers->Add(new Choice(gr->T("Install custom driver...")));
drivers->Add(new Choice(di->T("More information...")))->OnClick.Add([=](UI::EventParams &e) {
System_LaunchUrl(LaunchUrlType::BROWSER_URL, "https://www.ppsspp.org/docs/reference/custom-drivers/");
return UI::EVENT_DONE;
});
customDriverInstallChoice->OnClick.Handle(this, &DriverManagerScreen::OnCustomDriverInstall);
drivers->Add(new ItemHeader(gr->T("Drivers")));
bool isDefault = g_Config.sCustomDriver.empty();
drivers->Add(new DriverChoice("", isDefault))->OnUse.Handle(this, &DriverManagerScreen::OnCustomDriverChange);
const Path driverPath = GetDriverPath();
std::vector<File::FileInfo> listing;
if (File::GetFilesInDir(driverPath, &listing)) {
for (auto driver : listing) {
auto choice = drivers->Add(new DriverChoice(driver.name, g_Config.sCustomDriver == driver.name));
choice->OnUse.Handle(this, &DriverManagerScreen::OnCustomDriverChange);
choice->OnDelete.Handle(this, &DriverManagerScreen::OnCustomDriverUninstall);
}
}
drivers->Add(new Spacer(12.0));
}
UI::EventReturn DriverManagerScreen::OnCustomDriverChange(UI::EventParams &e) {
auto di = GetI18NCategory(I18NCat::DIALOG);
screenManager()->push(new PromptScreen(gamePath_, di->T("Changing this setting requires PPSSPP to restart."), di->T("Restart"), di->T("Cancel"), [=](bool yes) {
if (yes) {
INFO_LOG(G3D, "Switching driver to '%s'", e.s.c_str());
g_Config.sCustomDriver = e.s;
TriggerRestart("GameSettingsScreen::CustomDriverYes", false, gamePath_);
}
}));
return UI::EVENT_DONE;
}
UI::EventReturn DriverManagerScreen::OnCustomDriverUninstall(UI::EventParams &e) {
INFO_LOG(G3D, "Uninstalling driver: %s", e.s.c_str());
Path folder = GetDriverPath() / e.s;
File::DeleteDirRecursively(folder);
RecreateViews();
return UI::EVENT_DONE;
}
UI::EventReturn DriverManagerScreen::OnCustomDriverInstall(UI::EventParams &e) {
auto gr = GetI18NCategory(I18NCat::GRAPHICS);
System_BrowseForFile(GetRequesterToken(), gr->T("Install custom driver..."), BrowseFileType::ZIP, [this](const std::string &value, int) {
if (value.empty()) {
return;
}
auto gr = GetI18NCategory(I18NCat::GRAPHICS);
Path zipPath = Path(value);
// Don't bother checking the file extension. Can't always do that with files from Download (they have paths like content://com.android.providers.downloads.documents/document/msf%3A1000001095).
// Though, it may be possible to get it in other ways.
std::unique_ptr<ZipFileReader> zipFileReader = std::unique_ptr<ZipFileReader>(ZipFileReader::Create(zipPath, "", true));
if (!zipFileReader) {
g_OSD.Show(OSDType::MESSAGE_ERROR, gr->T("The chosen ZIP file doesn't contain a valid driver", "couldn't open zip"));
ERROR_LOG(SYSTEM, "Failed to open file '%s' as zip", zipPath.c_str());
return;
}
size_t metaDataSize;
uint8_t *metaData = zipFileReader->ReadFile("meta.json", &metaDataSize);
if (!metaData) {
g_OSD.Show(OSDType::MESSAGE_ERROR, gr->T("The chosen ZIP file doesn't contain a valid driver"), "meta.json missing");
return;
}
DriverMeta meta;
std::string errorStr;
if (!meta.Read(std::string_view((const char *)metaData, metaDataSize), &errorStr)) {
delete[] metaData;
g_OSD.Show(OSDType::MESSAGE_ERROR, gr->T("The chosen ZIP file doesn't contain a valid driver"), errorStr);
return;
}
delete[] metaData;
const Path newCustomDriver = GetDriverPath() / meta.name;
NOTICE_LOG(G3D, "Installing driver into '%s'", newCustomDriver.c_str());
File::CreateFullPath(newCustomDriver);
std::vector<File::FileInfo> zipListing;
zipFileReader->GetFileListing("", &zipListing, nullptr);
for (auto file : zipListing) {
File::CreateEmptyFile(newCustomDriver / file.name);
size_t size;
uint8_t *data = zipFileReader->ReadFile(file.name.c_str(), &size);
if (!data) {
g_OSD.Show(OSDType::MESSAGE_ERROR, gr->T("The chosen ZIP file doesn't contain a valid driver"), file.name.c_str());
return;
}
File::WriteDataToFile(false, data, size, newCustomDriver / file.name);
delete[] data;
}
auto iz = GetI18NCategory(I18NCat::INSTALLZIP);
g_OSD.Show(OSDType::MESSAGE_SUCCESS, iz->T("Installed!"));
RecreateViews();
});
return UI::EVENT_DONE;
}