#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::ReadTextFileToString(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 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(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) { if (e.s.empty()) { return UI::EVENT_DONE; } INFO_LOG(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 = std::unique_ptr(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(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(Log::G3D, "Installing driver into '%s'", newCustomDriver.c_str()); File::CreateFullPath(newCustomDriver); std::vector 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; }