diff --git a/CMakeLists.txt b/CMakeLists.txt index 4256bd2ab0bf..3fa828ff82df 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1455,6 +1455,8 @@ list(APPEND NativeAppSource UI/GameScreen.cpp UI/GameSettingsScreen.h UI/GameSettingsScreen.cpp + UI/DriverManagerScreen.h + UI/DriverManagerScreen.cpp UI/GPUDriverTestScreen.h UI/GPUDriverTestScreen.cpp UI/TiltAnalogSettingsScreen.h diff --git a/Common/Data/Format/JSONReader.cpp b/Common/Data/Format/JSONReader.cpp index b65381d986af..49ea00b559f7 100644 --- a/Common/Data/Format/JSONReader.cpp +++ b/Common/Data/Format/JSONReader.cpp @@ -67,7 +67,7 @@ const JsonNode *JsonGet::get(const char *child_name, JsonTag type) const { return nullptr; } -const char *JsonGet::getStringOrDie(const char *child_name) const { +const char *JsonGet::getStringOrNull(const char *child_name) const { const JsonNode *val = get(child_name, JSON_STRING); if (val) return val->value.toString(); @@ -75,7 +75,16 @@ const char *JsonGet::getStringOrDie(const char *child_name) const { return nullptr; } -const char *JsonGet::getString(const char *child_name, const char *default_value) const { +bool JsonGet::getString(const char *child_name, std::string *output) const { + const JsonNode *val = get(child_name, JSON_STRING); + if (!val) { + return false; + } + *output = val->value.toString(); + return true; +} + +const char *JsonGet::getStringOr(const char *child_name, const char *default_value) const { const JsonNode *val = get(child_name, JSON_STRING); if (!val) return default_value; diff --git a/Common/Data/Format/JSONReader.h b/Common/Data/Format/JSONReader.h index 44b33d25eed2..774f1c37c944 100644 --- a/Common/Data/Format/JSONReader.h +++ b/Common/Data/Format/JSONReader.h @@ -21,8 +21,9 @@ struct JsonGet { const JsonGet getDict(const char *child_name) const { return JsonGet(get(child_name, JSON_OBJECT)->value); } - const char *getStringOrDie(const char *child_name) const; - const char *getString(const char *child_name, const char *default_value) const; + const char *getStringOrNull(const char *child_name) const; + const char *getStringOr(const char *child_name, const char *default_value) const; + bool getString(const char *child_name, std::string *output) const; bool getStringVector(std::vector *vec) const; double getFloat(const char *child_name) const; double getFloat(const char *child_name, double default_value) const; @@ -46,7 +47,7 @@ struct JsonGet { class JsonReader { public: JsonReader(const std::string &filename); - // Makes a copy, after this returns you can free the input buffer. + // Makes a copy, after this returns you can free the input buffer. Zero termination is not necessary. JsonReader(const char *data, size_t size) { buffer_ = (char *)malloc(size + 1); if (buffer_) { diff --git a/Common/File/FileUtil.cpp b/Common/File/FileUtil.cpp index 184f45ebfdcb..d9270f5d2d7f 100644 --- a/Common/File/FileUtil.cpp +++ b/Common/File/FileUtil.cpp @@ -62,6 +62,7 @@ #include #include #include +#include #endif #if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__NetBSD__) @@ -990,6 +991,19 @@ bool OpenFileInEditor(const Path &fileName) { return true; } +const Path GetCurDirectory() { +#ifdef _WIN32 + wchar_t buffer[4096]; + size_t len = GetCurrentDirectory(sizeof(buffer) / sizeof(wchar_t), buffer); + std::string curDir = ConvertWStringToUTF8(buffer); + return Path(curDir); +#else + char temp[4096]{}; + getcwd(temp, 4096); + return Path(temp); +#endif +} + const Path &GetExeDirectory() { static Path ExePath; diff --git a/Common/File/FileUtil.h b/Common/File/FileUtil.h index e25b93521b90..225850b26d47 100644 --- a/Common/File/FileUtil.h +++ b/Common/File/FileUtil.h @@ -123,6 +123,8 @@ bool OpenFileInEditor(const Path &fileName); // TODO: Belongs in System or something. const Path &GetExeDirectory(); +const Path GetCurDirectory(); + // simple wrapper for cstdlib file functions to // hopefully will make error checking easier // and make forgetting an fclose() harder diff --git a/Common/GPU/Vulkan/VulkanContext.cpp b/Common/GPU/Vulkan/VulkanContext.cpp index 6fdb98c04523..cf951d89d489 100644 --- a/Common/GPU/Vulkan/VulkanContext.cpp +++ b/Common/GPU/Vulkan/VulkanContext.cpp @@ -133,7 +133,7 @@ VkResult VulkanContext::CreateInstance(const CreateInfo &info) { #endif #endif - if ((flags_ & VULKAN_FLAG_VALIDATE) && g_Config.customDriver.empty()) { + if ((flags_ & VULKAN_FLAG_VALIDATE) && g_Config.sCustomDriver.empty()) { if (IsInstanceExtensionAvailable(VK_EXT_DEBUG_UTILS_EXTENSION_NAME)) { // Enable the validation layers for (size_t i = 0; i < ARRAY_SIZE(validationLayers); i++) { diff --git a/Common/GPU/Vulkan/VulkanLoader.cpp b/Common/GPU/Vulkan/VulkanLoader.cpp index 153c5b73f706..6cab42ef55fc 100644 --- a/Common/GPU/Vulkan/VulkanLoader.cpp +++ b/Common/GPU/Vulkan/VulkanLoader.cpp @@ -300,40 +300,42 @@ static VulkanLibraryHandle VulkanLoadLibrary(std::string *errorString) { void *lib = nullptr; #if PPSSPP_PLATFORM(ANDROID) && PPSSPP_ARCH(ARM64) - if (!g_Config.customDriver.empty() && g_Config.customDriver != "Default") { - const Path driverPath = g_Config.internalDataDirectory / "drivers" / g_Config.customDriver; - - json::JsonReader meta = json::JsonReader((driverPath / "meta.json").c_str()); - if (meta.ok()) { - std::string driverLibName = meta.root().get("libraryName")->value.toString(); - - Path tempDir = g_Config.internalDataDirectory / "temp"; - Path fileRedirectDir = g_Config.internalDataDirectory / "vk_file_redirect"; - - File::CreateDir(tempDir); - File::CreateDir(fileRedirectDir); - - lib = adrenotools_open_libvulkan( - RTLD_NOW | RTLD_LOCAL, ADRENOTOOLS_DRIVER_FILE_REDIRECT | ADRENOTOOLS_DRIVER_CUSTOM, - (std::string(tempDir.c_str()) + "/").c_str(),g_nativeLibDir.c_str(), - (std::string(driverPath.c_str()) + "/").c_str(),driverLibName.c_str(), - (std::string(fileRedirectDir.c_str()) + "/").c_str(),nullptr); - if (!lib) { - ERROR_LOG(G3D, "Failed to load custom driver"); - } - } - } + if (!g_Config.sCustomDriver.empty() && g_Config.sCustomDriver != "Default") { + const Path driverPath = g_Config.internalDataDirectory / "drivers" / g_Config.sCustomDriver; + + json::JsonReader meta = json::JsonReader((driverPath / "meta.json").c_str()); + if (meta.ok()) { + std::string driverLibName = meta.root().get("libraryName")->value.toString(); + + Path tempDir = g_Config.internalDataDirectory / "temp"; + Path fileRedirectDir = g_Config.internalDataDirectory / "vk_file_redirect"; + + File::CreateDir(tempDir); + File::CreateDir(fileRedirectDir); + + lib = adrenotools_open_libvulkan( + RTLD_NOW | RTLD_LOCAL, ADRENOTOOLS_DRIVER_FILE_REDIRECT | ADRENOTOOLS_DRIVER_CUSTOM, + (std::string(tempDir.c_str()) + "/").c_str(), g_nativeLibDir.c_str(), + (std::string(driverPath.c_str()) + "/").c_str(), driverLibName.c_str(), + (std::string(fileRedirectDir.c_str()) + "/").c_str(), nullptr); + if (!lib) { + ERROR_LOG(G3D, "Failed to load custom driver with AdrenoTools ('%s')", g_Config.sCustomDriver.c_str()); + } else { + INFO_LOG(G3D, "Vulkan library loaded with AdrenoTools ('%s')", g_Config.sCustomDriver.c_str()); + } + } + } #endif - if (!lib) { - for (int i = 0; i < ARRAY_SIZE(so_names); i++) { - lib = dlopen(so_names[i], RTLD_NOW | RTLD_LOCAL); - if (lib) { - INFO_LOG(G3D, "Vulkan library loaded with AdrenoTools ('%s')", so_names[i]); - break; - } - } - } + if (!lib) { + for (int i = 0; i < ARRAY_SIZE(so_names); i++) { + lib = dlopen(so_names[i], RTLD_NOW | RTLD_LOCAL); + if (lib) { + INFO_LOG(G3D, "Vulkan library loaded ('%s')", so_names[i]); + break; + } + } +} return lib; #endif } diff --git a/Common/UI/View.cpp b/Common/UI/View.cpp index 2e5bd83cde27..848f5dac9fa7 100644 --- a/Common/UI/View.cpp +++ b/Common/UI/View.cpp @@ -599,7 +599,7 @@ ItemHeader::ItemHeader(const std::string &text, LayoutParams *layoutParams) } void ItemHeader::Draw(UIContext &dc) { - dc.SetFontStyle(dc.theme->uiFontSmall); + dc.SetFontStyle(large_ ? dc.theme->uiFont : dc.theme->uiFontSmall); dc.DrawText(text_.c_str(), bounds_.x + 4, bounds_.centerY(), dc.theme->headerStyle.fgColor, ALIGN_LEFT | ALIGN_VCENTER); dc.Draw()->DrawImageCenterTexel(dc.theme->whiteImage, bounds_.x, bounds_.y2()-2, bounds_.x2(), bounds_.y2(), dc.theme->headerStyle.fgColor); } diff --git a/Common/UI/View.h b/Common/UI/View.h index 8ba09a00e9ce..bf2012dc767a 100644 --- a/Common/UI/View.h +++ b/Common/UI/View.h @@ -848,9 +848,10 @@ class ItemHeader : public Item { void Draw(UIContext &dc) override; std::string DescribeText() const override; void GetContentDimensionsBySpec(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert, float &w, float &h) const override; - + void SetLarge(bool large) { large_ = large; } private: std::string text_; + bool large_ = false; }; class PopupHeader : public Item { diff --git a/Core/Config.cpp b/Core/Config.cpp index 5c1d60392752..50ea0578d8d4 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -592,7 +592,7 @@ static const ConfigSetting graphicsSettings[] = { ConfigSetting("iShowStatusFlags", &g_Config.iShowStatusFlags, 0, CfgFlag::PER_GAME), ConfigSetting("GraphicsBackend", &g_Config.iGPUBackend, &DefaultGPUBackend, &GPUBackendTranslator::To, &GPUBackendTranslator::From, CfgFlag::DEFAULT | CfgFlag::REPORT), #if PPSSPP_PLATFORM(ANDROID) && PPSSPP_ARCH(ARM64) - ConfigSetting("CustomDriver", &g_Config.customDriver, "", CfgFlag::DEFAULT), + ConfigSetting("CustomDriver", &g_Config.sCustomDriver, "", CfgFlag::DEFAULT), #endif ConfigSetting("FailedGraphicsBackends", &g_Config.sFailedGPUBackends, "", CfgFlag::DEFAULT), ConfigSetting("DisabledGraphicsBackends", &g_Config.sDisabledGPUBackends, "", CfgFlag::DEFAULT), @@ -1399,6 +1399,11 @@ void Config::PostLoadCleanup(bool gameSpecific) { if (iTexScalingLevel <= 0) { iTexScalingLevel = 1; } + + // Remove a legacy value. + if (g_Config.sCustomDriver == "Default") { + g_Config.sCustomDriver = ""; + } } void Config::PreSaveCleanup(bool gameSpecific) { @@ -1448,7 +1453,8 @@ void Config::DownloadCompletedCallback(http::Request &download) { return; } - std::string version = root.getString("version", ""); + std::string version; + root.getString("version", &version); const char *gitVer = PPSSPP_GIT_VERSION; Version installed(gitVer); diff --git a/Core/Config.h b/Core/Config.h index 8fc8f8309e8d..c97c2f65da34 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -149,7 +149,7 @@ struct Config { // GFX int iGPUBackend; - std::string customDriver; + std::string sCustomDriver; std::string sFailedGPUBackends; std::string sDisabledGPUBackends; // We have separate device parameters for each backend so it doesn't get erased if you switch backends. diff --git a/Core/Debugger/WebSocket.cpp b/Core/Debugger/WebSocket.cpp index c17aa57c7067..4da6366d242d 100644 --- a/Core/Debugger/WebSocket.cpp +++ b/Core/Debugger/WebSocket.cpp @@ -165,7 +165,7 @@ void HandleDebuggerRequest(const http::ServerRequest &request) { } const JsonGet root = reader.root(); - const char *event = root ? root.getString("event", nullptr) : nullptr; + const char *event = root ? root.getStringOr("event", nullptr) : nullptr; if (!event) { ws->Send(DebuggerErrorEvent("Bad message: no event property", LogLevel::LERROR, root)); return; diff --git a/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp b/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp index c8a962b3c42d..0e0286b39494 100644 --- a/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp +++ b/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp @@ -219,7 +219,7 @@ static DebuggerRegType ValidateRegName(DebuggerRequest &req, const std::string & } static DebuggerRegType ValidateCatReg(DebuggerRequest &req, int *cat, int *reg) { - const char *name = req.data.getString("name", nullptr); + const char *name = req.data.getStringOr("name", nullptr); if (name) return ValidateRegName(req, name, cat, reg); diff --git a/UI/DriverManagerScreen.cpp b/UI/DriverManagerScreen.cpp new file mode 100644 index 000000000000..e98715b0f843 --- /dev/null +++ b/UI/DriverManagerScreen.cpp @@ -0,0 +1,268 @@ +#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; + } + + 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); + + // Read the meta data + DriverMeta meta{}; + bool isDefault = driverName.empty(); + if (isDefault) { + meta.description = "The default installed driver on your system"; + } + + 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 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(gr->T("Use this driver")))->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(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(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(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 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; +} diff --git a/UI/DriverManagerScreen.h b/UI/DriverManagerScreen.h new file mode 100644 index 000000000000..d83b7b461d21 --- /dev/null +++ b/UI/DriverManagerScreen.h @@ -0,0 +1,26 @@ +#pragma once + +#include "ppsspp_config.h" + +#include "Common/UI/UIScreen.h" +#include "UI/MiscScreens.h" +#include "UI/TabbedDialogScreen.h" + +// Per-game settings screen - enables you to configure graphic options, control options, etc +// per game. +class DriverManagerScreen : public TabbedUIDialogScreenWithGameBackground { +public: + DriverManagerScreen(const Path &gamePath); + + const char *tag() const override { return "DriverManagerScreen"; } + +protected: + void CreateTabs() override; + +private: + UI::EventReturn OnCustomDriverInstall(UI::EventParams &e); + UI::EventReturn OnCustomDriverUninstall(UI::EventParams &e); + UI::EventReturn OnCustomDriverChange(UI::EventParams &e); + + void CreateDriverTab(UI::ViewGroup *drivers); +}; diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index f1187c14674d..265ba6afbb93 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -39,6 +39,7 @@ #include "Common/Data/Text/I18n.h" #include "Common/Data/Encoding/Utf8.h" #include "UI/EmuScreen.h" +#include "UI/DriverManagerScreen.h" #include "UI/GameSettingsScreen.h" #include "UI/GameInfoCache.h" #include "UI/GamepadEmu.h" @@ -57,8 +58,6 @@ #include "UI/RetroAchievementScreens.h" #include "Common/File/FileUtil.h" -#include "Common/File/VFS/ZipFileReader.h" -#include "Common/Data/Format/JSONReader.h" #include "Common/File/AndroidContentURI.h" #include "Common/OSVersion.h" #include "Common/TimeUtil.h" @@ -103,20 +102,22 @@ static bool CheckKgslPresent() { return access(KgslPath, F_OK) == 0; } -bool SupportsCustomDriver() { +static bool SupportsCustomDriver() { return android_get_device_api_level() >= 28 && CheckKgslPresent(); } #else -bool SupportsCustomDriver() { +static bool SupportsCustomDriver() { +#ifdef _DEBUG + return true; +#else return false; +#endif } #endif -static void TriggerRestart(const char *why, bool editThenRestore, const Path &gamePath); - GameSettingsScreen::GameSettingsScreen(const Path &gamePath, std::string gameID, bool editThenRestore) : TabbedUIDialogScreenWithGameBackground(gamePath), gameID_(gameID), editThenRestore_(editThenRestore) { prevInflightFrames_ = g_Config.iInflightFrames; @@ -1254,93 +1255,6 @@ void GameSettingsScreen::CreateVRSettings(UI::ViewGroup *vrSettings) { vrSettings->Add(new PopupMultiChoice(&g_Config.iCameraPitch, vr->T("Camera type"), cameraPitchModes, 0, 3, I18NCat::NONE, screenManager())); } -UI::EventReturn DeveloperToolsScreen::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) { - TriggerRestart("GameSettingsScreen::CustomDriverYes", false, gamePath_); - } - })); - return UI::EVENT_DONE; -} - -UI::EventReturn DeveloperToolsScreen::OnCustomDriverInstall(UI::EventParams &e) { - auto gr = GetI18NCategory(I18NCat::GRAPHICS); - - System_BrowseForFile(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(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; - } - - // Validate the json file. TODO: Be a bit more detailed. - json::JsonReader meta = json::JsonReader((const char *)metaData, metaDataSize); - delete[] metaData; - if (!meta.ok()) { - g_OSD.Show(OSDType::MESSAGE_ERROR, gr->T("The chosen ZIP file doesn't contain a valid driver"), "meta.json not valid json"); - return; - } - - const JsonNode *nameNode = meta.root().get("name"); - if (!nameNode) { - g_OSD.Show(OSDType::MESSAGE_ERROR, gr->T("The chosen ZIP file doesn't contain a valid driver"), "missing driver name in json"); - return; - } - - std::string driverName = nameNode->value.toString(); - if (driverName.empty()) { - g_OSD.Show(OSDType::MESSAGE_ERROR, gr->T("The chosen ZIP file doesn't contain a valid driver"), "driver name empty"); - return; - } - - const Path newCustomDriver = g_Config.internalDataDirectory / "drivers" / driverName; - NOTICE_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; -} - UI::EventReturn GameSettingsScreen::OnAutoFrameskip(UI::EventParams &e) { g_Config.UpdateAfterSettingAutoFrameSkip(); return UI::EVENT_DONE; @@ -1562,7 +1476,7 @@ void GameSettingsScreen::CallbackMemstickFolder(bool yes) { } } -static void TriggerRestart(const char *why, bool editThenRestore, const Path &gamePath) { +void TriggerRestart(const char *why, bool editThenRestore, const Path &gamePath) { // Extra save here to make sure the choice really gets saved even if there are shutdown bugs in // the GPU backend code. g_Config.Save(why); @@ -1835,22 +1749,11 @@ void DeveloperToolsScreen::CreateViews() { } if (GetGPUBackend() == GPUBackend::VULKAN && SupportsCustomDriver()) { - const Path driverPath = g_Config.internalDataDirectory / "drivers"; - - std::vector listing; - File::GetFilesInDir(driverPath, &listing); - - std::vector availableDrivers; - availableDrivers.push_back("Default"); - - for (auto driver : listing) { - availableDrivers.push_back(driver.name); - } - auto driverChoice = list->Add(new PopupMultiChoiceDynamic(&g_Config.customDriver, gr->T("Current GPU Driver"), availableDrivers, I18NCat::NONE, screenManager())); - driverChoice->OnChoice.Handle(this, &DeveloperToolsScreen::OnCustomDriverChange); - - auto customDriverInstallChoice = list->Add(new Choice(gr->T("Install Custom Driver..."))); - customDriverInstallChoice->OnClick.Handle(this, &DeveloperToolsScreen::OnCustomDriverInstall); + auto driverChoice = list->Add(new Choice(gr->T("Adreno Driver Manager"))); + driverChoice->OnClick.Add([=](UI::EventParams &e) { + screenManager()->push(new DriverManagerScreen(gamePath_)); + return UI::EVENT_DONE; + }); } // For now, we only implement GPU driver tests for Vulkan and OpenGL. This is simply diff --git a/UI/GameSettingsScreen.h b/UI/GameSettingsScreen.h index 28d58277ac64..f7cdbef4d6af 100644 --- a/UI/GameSettingsScreen.h +++ b/UI/GameSettingsScreen.h @@ -27,6 +27,8 @@ #include "UI/MiscScreens.h" #include "UI/TabbedDialogScreen.h" +class Path; + // Per-game settings screen - enables you to configure graphic options, control options, etc // per game. class GameSettingsScreen : public TabbedUIDialogScreenWithGameBackground { @@ -151,8 +153,6 @@ class DeveloperToolsScreen : public UIDialogScreenWithGameBackground { UI::EventReturn OnFramedumpTest(UI::EventParams &e); UI::EventReturn OnTouchscreenTest(UI::EventParams &e); UI::EventReturn OnCopyStatesToRoot(UI::EventParams &e); - UI::EventReturn OnCustomDriverChange(UI::EventParams &e); - UI::EventReturn OnCustomDriverInstall(UI::EventParams &e); bool allowDebugger_ = false; bool canAllowDebugger_ = true; @@ -240,3 +240,5 @@ class RestoreSettingsScreen : public PopupScreen { void OnCompleted(DialogResult result) override; int restoreFlags_ = (int)(RestoreSettingsBits::SETTINGS); // RestoreSettingsBits enum }; + +void TriggerRestart(const char *why, bool editThenRestore, const Path &gamePath); diff --git a/UI/RemoteISOScreen.cpp b/UI/RemoteISOScreen.cpp index f5042707119b..0a170d60cb60 100644 --- a/UI/RemoteISOScreen.cpp +++ b/UI/RemoteISOScreen.cpp @@ -234,7 +234,7 @@ bool RemoteISOConnectScreen::FindServer(std::string &resultHost, int &resultPort if (scanCancelled) return false; - const char *host = entry.getString("ip", ""); + const char *host = entry.getStringOr("ip", ""); int port = entry.getInt("p", 0); if (TryServer(host, port)) { diff --git a/UI/Store.cpp b/UI/Store.cpp index c1701301b6e6..905519c857b4 100644 --- a/UI/Store.cpp +++ b/UI/Store.cpp @@ -475,12 +475,12 @@ void StoreScreen::ParseListing(const std::string &json) { e.type = ENTRY_PBPZIP; e.name = GetTranslatedString(game, "name"); e.description = GetTranslatedString(game, "description", ""); - e.author = game.getString("author", "?"); + e.author = game.getStringOr("author", "?"); e.size = game.getInt("size"); - e.downloadURL = game.getString("download-url", ""); - e.iconURL = game.getString("icon-url", ""); + e.downloadURL = game.getStringOr("download-url", ""); + e.iconURL = game.getStringOr("icon-url", ""); e.hidden = false; // NOTE: Handling of the "hidden" flag is broken in old versions of PPSSPP. Do not use. - const char *file = game.getString("file", nullptr); + const char *file = game.getStringOr("file", nullptr); if (!file) continue; e.file = file; @@ -596,7 +596,7 @@ std::string StoreScreen::GetTranslatedString(const json::JsonGet json, const std } const char *str = nullptr; if (dict) { - str = dict.getString(key.c_str(), nullptr); + str = dict.getStringOr(key.c_str(), nullptr); } if (str) { return std::string(str); diff --git a/UI/UI.vcxproj b/UI/UI.vcxproj index bc54fd4ced33..aff7187e61a4 100644 --- a/UI/UI.vcxproj +++ b/UI/UI.vcxproj @@ -44,6 +44,7 @@ + @@ -81,6 +82,7 @@ + diff --git a/UI/UI.vcxproj.filters b/UI/UI.vcxproj.filters index 5c1d68dab97f..24c3cc6cf083 100644 --- a/UI/UI.vcxproj.filters +++ b/UI/UI.vcxproj.filters @@ -92,6 +92,9 @@ Screens + + Screens + @@ -184,6 +187,9 @@ Screens + + Screens + diff --git a/UWP/UI_UWP/UI_UWP.vcxproj b/UWP/UI_UWP/UI_UWP.vcxproj index e6940e76f2f0..f3b4c5a08546 100644 --- a/UWP/UI_UWP/UI_UWP.vcxproj +++ b/UWP/UI_UWP/UI_UWP.vcxproj @@ -119,6 +119,7 @@ + @@ -157,6 +158,7 @@ + diff --git a/UWP/UI_UWP/UI_UWP.vcxproj.filters b/UWP/UI_UWP/UI_UWP.vcxproj.filters index aa3b1b3b6b54..18fe8834c364 100644 --- a/UWP/UI_UWP/UI_UWP.vcxproj.filters +++ b/UWP/UI_UWP/UI_UWP.vcxproj.filters @@ -37,6 +37,7 @@ + @@ -75,5 +76,6 @@ + \ No newline at end of file diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 78560cbb723c..36a8e4c46e69 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -812,6 +812,7 @@ LOCAL_SRC_FILES := \ $(SRC)/UI/ChatScreen.cpp \ $(SRC)/UI/DebugOverlay.cpp \ $(SRC)/UI/DevScreens.cpp \ + $(SRC)/UI/DriverManagerScreen.cpp \ $(SRC)/UI/DisplayLayoutScreen.cpp \ $(SRC)/UI/EmuScreen.cpp \ $(SRC)/UI/MainScreen.cpp \