Files
ppsspp/UI/DeveloperSettingsPages.cpp
T
Henrik RydgårdandClaude Opus 5.5 a2170728f5 UI: Look up strings in the sections where they're translated
These were looked up in a different section from the one with the translation,
so they always showed in English (the savedata error messages, "Done!" after
moving the memstick, "Switch UMD", "Ask for network permission" and others).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-26 16:20:03 +01:00

773 lines
28 KiB
C++

// 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 <string>
#include "android/jni/app-android.h"
#include "Common/UI/View.h"
#include "Common/UI/ViewGroup.h"
#include "Common/System/OSD.h"
#include "Common/System/Request.h"
#include "Common/GPU/OpenGL/GLFeatures.h"
#include "Common/Data/Text/Parsers.h"
#include "Common/Data/Encoding/Utf8.h"
#include "Common/File/FileUtil.h"
#include "Common/Render/Text/draw_text.h"
#include "Common/StringUtils.h"
#include "Common/UI/ScreenManager.h"
#include "GPU/Common/TextureReplacer.h"
#include "GPU/Common/PostShader.h"
#include "Core/MIPS/MIPSTracer.h"
#include "Core/ELF/ParamSFO.h"
#include "Core/Config.h"
#include "Core/HLE/HLE.h"
#include "Core/Core.h"
#include "Core/System.h"
#include "Core/WebServer.h"
#include "Core/Util/PathUtil.h"
#include "Core/FileSystems/VirtualDiscFileSystem.h"
#include "UI/GPUDriverTestScreen.h"
#include "UI/DeveloperSettingsPages.h"
#include "UI/DevScreens.h"
#include "UI/DriverManagerScreen.h"
#include "UI/DisplayLayoutScreen.h"
#include "UI/GameSettingsScreen.h"
#include "UI/OnScreenDisplay.h"
#include "UI/IconCache.h"
#include "UI/MiscViews.h"
#include "UI/SettingsPage.h"
#if PPSSPP_PLATFORM(ANDROID)
static bool CheckKgslPresent() {
constexpr auto KgslPath{ "/dev/kgsl-3d0" };
return access(KgslPath, F_OK) == 0;
}
static bool SupportsCustomDriver() {
return android_get_device_api_level() >= 28 && CheckKgslPresent();
}
#else
static bool SupportsCustomDriver() {
#ifdef _DEBUG
return false; // change to true to debug driver installation on other platforms
#else
return false;
#endif
}
#endif
static std::string PostShaderTranslateName(std::string_view value) {
const ShaderInfo *info = GetPostShaderInfo(value);
if (info) {
auto ps = GetI18NCategory(I18NCat::POSTSHADERS);
return std::string(ps->T(value, info->name));
} else {
return std::string(value);
}
}
enum class HasIni {
NO,
YES,
MAYBE,
};
// The pages' handlers, defined after them.
static void OnLoggingChanged(UI::EventParams &e);
static void OpenTexturesIniFile(HasIni *hasIni);
static void OnJitDebugTools(SettingsPageHost *host);
static void OnGPUDriverTest(SettingsPageHost *host);
static void OnJitAffectingSetting(UI::EventParams &e);
#if PPSSPP_PLATFORM(IOS)
static void OnCopyStatesToRoot(UI::EventParams &e);
#endif
static void SetRemoteDebuggerAllowed(bool allow);
#if !PPSSPP_PLATFORM(ANDROID) && !PPSSPP_PLATFORM(IOS) && !PPSSPP_PLATFORM(SWITCH)
static void SetMIPSTracerEnabled(bool enabled);
static void OnMIPSTracerPathChanged(SettingsPageHost *host);
static void OnMIPSTracerFlushTrace(UI::EventParams &e);
static void OnMIPSTracerClearJitCache(UI::EventParams &e);
static void OnMIPSTracerClearTracer(UI::EventParams &e);
#endif
static void BuildTextureReplacementPage(SettingsPage *page) {
using namespace UI;
page->Header("Texture Replacement");
page->Setting(&g_Config.bSaveNewTextures);
page->Setting(&g_Config.bReplaceTextures);
// Checked once per build of the page, when first needed.
std::shared_ptr<HasIni> hasTexturesIni = std::make_shared<HasIni>(HasIni::MAYBE);
page->Choice("Create/Open textures.ini file for current game").OnClick([hasTexturesIni](UI::EventParams &e) {
OpenTexturesIniFile(hasTexturesIni.get());
}).EnabledFunc([hasTexturesIni] {
if (!PSP_IsInited())
return false;
// Disable the choice to Open/Create if the textures.ini file already exists, and we can't open it due to platform support limitations.
if (!System_GetPropertyBool(SYSPROP_SUPPORTS_OPEN_FILE_IN_EDITOR)) {
if (*hasTexturesIni == HasIni::MAYBE)
*hasTexturesIni = TextureReplacer::IniExists(g_paramSFO.GetDiscID()) ? HasIni::YES : HasIni::NO;
return *hasTexturesIni != HasIni::YES;
}
return true;
});
if (System_GetPropertyBool(SYSPROP_CAN_SHOW_FILE)) {
// Best string we have
page->Choice({ I18NCat::DIALOG, "Show in folder" }).OnClick([](UI::EventParams &) {
Path path;
if (PSP_IsInited()) {
std::string gameID = g_paramSFO.GetDiscID();
path = GetSysDirectory(DIRECTORY_TEXTURES) / gameID;
} else {
// Just show the root textures directory.
path = GetSysDirectory(DIRECTORY_TEXTURES);
}
System_ShowFileInFolder(path);
});
}
page->Setting(&g_Config.iReplacementTextureLoadSpeed).Setup([](View *view) {
static_cast<PopupMultiChoice *>(view)->SetChoiceIcon(3, ImageID("I_WARNING"));
});
}
static void BuildGeneralPage(SettingsPage *page) {
using namespace UI;
SettingsPageHost *host = page->Host();
page->Header({ I18NCat::SYSTEM, "CPU Core" });
bool canUseJit = System_GetPropertyBool(SYSPROP_CAN_JIT);
// iOS can now use JIT on all modes, apparently.
// The bool may come in handy for future non-jit platforms though (UWP XB1?)
// In iOS App Store builds, we disable the JIT.
page->Setting(&g_Config.iCpuCore).OnChange([](UI::EventParams &e) {
OnJitAffectingSetting(e);
g_Config.NotifyUpdatedCpuCore();
}).Setup([canUseJit](View *view) {
PopupMultiChoice *core = static_cast<PopupMultiChoice *>(view);
if (!canUseJit) {
core->HideChoice(1);
}
// Every native JIT is IR-based, so "JIT using IR" is the same as "JIT".
core->HideChoice(3);
});
// Only the interpreter raises these, but don't hide it behind the CPU core choice - it'd just
// look like the setting had vanished.
page->Setting(&g_Config.bEnableFPUExceptionTraps);
// Experimental, so not translated yet.
page->Setting(&g_Config.bEmulateMediaEngine);
page->Choice("JIT debug tools").OnClick([host](UI::EventParams &e) {
OnJitDebugTools(host);
});
page->Setting(&g_Config.bShowDeveloperMenu);
page->Custom("Debug overlay", [host](UI::ViewGroup *parent) -> UI::View * {
AddOverlayList(parent, host->PageScreenManager());
return parent->GetViewByIndex(parent->GetNumSubviews() - 1);
});
page->Header({ I18NCat::MAINSETTINGS, "General" });
page->Bool(&g_Config.bEnableLogging, "Enable Logging").OnChange(&OnLoggingChanged);
page->Choice("Logging Channels").OnClick([host](UI::EventParams &e) {
host->PageScreenManager()->push(new LogConfigScreen());
});
page->Setting(&g_Config.bEnableFileLogging).Enabled(&g_Config.bEnableLogging);
if (System_GetPropertyInt(SYSPROP_DEVICE_TYPE) == DEVICE_TYPE_DESKTOP) {
page->Choice("Show log file in folder").OnClick([](UI::EventParams &e) {
Path logFilePath = g_logManager.GetLogFilePath();
if (logFilePath.empty()) {
ERROR_LOG(Log::System, "No log file path configured.");
return;
}
if (File::Exists(logFilePath)) {
System_ShowFileInFolder(logFilePath);
} else {
System_LaunchUrl(LaunchUrlType::LOCAL_FILE, logFilePath.NavigateUp().ToString());
}
});
}
page->Setting(&g_Config.bLogFrameDrops);
if (GetGPUBackend() == GPUBackend::VULKAN) {
page->Setting(&g_Config.bGpuLogProfiler);
}
auto debuggerAllowed = [] {
return !WebServerStopped(WebServerFlags::DEBUGGER);
};
page->Bool("Allow remote debugger", debuggerAllowed, &SetRemoteDebuggerAllowed).EnabledFunc([] {
return !WebServerStopping(WebServerFlags::DEBUGGER);
});
page->Setting(&g_Config.bRemoteDebuggerLocal).EnabledFunc(debuggerAllowed);
page->Setting(&g_Config.bAutoSaveLoadSymbols);
page->Choice("GPI/GPO switches/LEDs").OnClick([host](UI::EventParams &e) {
host->PageScreenManager()->push(new GPIGPOScreen(T(I18NCat::DEVELOPER, "GPI/GPO switches/LEDs")));
});
page->Setting(&g_Config.bShowSaveLoadIndicator);
#if PPSSPP_PLATFORM(ANDROID)
page->Setting(&g_Config.iDisplayFramerateMode).EnabledFunc([]() {
return System_GetPropertyInt(SYSPROP_SYSTEMVERSION) >= 30;
}).OnChange([](UI::EventParams &e) {
System_Notify(SystemNotification::FORCE_RECREATE_ACTIVITY);
});
#endif
#if PPSSPP_PLATFORM(IOS)
page->Custom({ I18NCat::MEMSTICK, "Moving the memstick directory is NOT recommended on iOS" }, [](UI::ViewGroup *parent) -> UI::View * {
NoticeView *notice = parent->Add(new NoticeView(NoticeLevel::WARN, T(I18NCat::MEMSTICK, "Moving the memstick directory is NOT recommended on iOS"), ""));
notice->SetWrapText(true);
return notice;
});
page->Choice({ I18NCat::SYSTEM, "Set Memory Stick folder" }).OnClick([host](UI::EventParams &) {
SetMemStickDirDarwin(host->PageRequesterToken());
});
#endif
// Makes it easy to get savestates out of an iOS device. The file listing shown in MacOS doesn't allow
// you to descend into directories.
#if PPSSPP_PLATFORM(IOS)
page->Choice("Copy savestates to memstick root").OnClick(&OnCopyStatesToRoot);
#endif
// Reuse strings to the max, heh.
page->Custom({ I18NCat::DIALOG, "Copy to clipboard" }, [](UI::ViewGroup *parent) -> UI::View * {
Choice *copy = parent->Add(new Choice(ApplySafeSubstitutions("%1: ppsspp.ini", T(I18NCat::DIALOG, "Copy to clipboard"))));
copy->OnClick.Add([](UI::EventParams &) {
auto di = GetI18NCategory(I18NCat::DIALOG);
std::string configStr = g_Config.GetConfigAsString();
if (!configStr.empty()) {
System_CopyStringToClipboard(configStr);
g_OSD.Show(OSDType::MESSAGE_INFO, ApplySafeSubstitutions(di->T("Copied to clipboard: %1"), "ppsspp.ini"), 0.0f, "copyToClip");
}
});
return copy;
});
#if PLATFORM_SUPPORTS_FILE_HANDLER_PLUGINS
page->Setting(&g_Config.bEnableFileHandlerPlugins);
#endif
}
static void BuildTestsPage(SettingsPage *page) {
using namespace UI;
SettingsPageHost *host = page->Host();
page->Choice("Touchscreen Test").OnClick([host](UI::EventParams &e) {
host->PageScreenManager()->push(new TouchTestScreen(host->PageGamePath()));
});
page->Choice("Framedump tests").OnClick([host](UI::EventParams &e) {
host->PageScreenManager()->push(new FrameDumpTestScreen());
}).EnabledFunc([] {
return !PSP_IsInited();
});
// For now, we only implement GPU driver tests for Vulkan and OpenGL. This is simply
// because the D3D drivers are generally solid enough to not need this type of investigation.
if (g_Config.iGPUBackend == (int)GPUBackend::VULKAN || g_Config.iGPUBackend == (int)GPUBackend::OPENGL) {
page->Choice("GPU Driver Test").OnClick([host](UI::EventParams &e) {
OnGPUDriverTest(host);
});
}
}
static void BuildDumpFilePage(SettingsPage *page) {
using namespace UI;
auto addDumpType = [page](DumpFileType type, SettingText text) {
page->Custom(text, [type, text](UI::ViewGroup *parent) -> UI::View * {
return parent->Add(new BitCheckBox(&g_Config.iDumpFileTypes, (int)type, text.Translate(I18NCat::DEVELOPER)));
});
};
page->Header("Dump files");
addDumpType(DumpFileType::EBOOT, { I18NCat::DEVELOPER, "Dump Decrypted Eboot", "Dump Decrypted EBOOT.BIN (If Encrypted) When Booting Game" });
addDumpType(DumpFileType::PRX, "PRX");
addDumpType(DumpFileType::Atrac3, "Atrac3/3+");
addDumpType(DumpFileType::PBP_ISO, "ISO from PBP");
}
static void BuildHLEPage(SettingsPage *page) {
using namespace UI;
page->Setting(&g_Config.bUseOldAtrac);
auto addModule = [page](int *flags, DisableHLEFlags flag, const char *modname) {
page->Custom(SettingText::Raw(modname), [flags, flag, modname](UI::ViewGroup *parent) -> UI::View * {
return parent->Add(new BitCheckBox(flags, (int)flag, modname));
}).EnabledFunc([] {
return !PSP_IsInited();
});
};
page->Header("Disable HLE");
for (int i = 0; i < (int)DisableHLEFlags::Count; i++) {
DisableHLEFlags flag = (DisableHLEFlags)(1 << i);
// Show a checkbox, unless the setting has graduated to always disabled.
if (!(flag & AlwaysDisableHLEFlags())) {
const HLEModuleMeta *meta = GetHLEModuleMetaByFlag(flag);
if (meta) {
addModule(&g_Config.iDisableHLE, flag, meta->modname);
}
}
}
page->Header("Force-enable HLE");
for (int i = 0; i < (int)DisableHLEFlags::Count; i++) {
DisableHLEFlags flag = (DisableHLEFlags)(1 << i);
// Show a checkbox, only if the setting has graduated to always disabled (and thus it makes sense to force-enable it).
if (flag & AlwaysDisableHLEFlags()) {
const HLEModuleMeta *meta = GetHLEModuleMetaByFlag(flag);
if (meta) {
addModule(&g_Config.iForceEnableHLE, flag, meta->modname);
}
}
}
}
#if !PPSSPP_PLATFORM(ANDROID) && !PPSSPP_PLATFORM(IOS) && !PPSSPP_PLATFORM(SWITCH)
static void BuildMIPSTracerPage(SettingsPage *page) {
using namespace UI;
SettingsPageHost *host = page->Host();
page->Header("MIPSTracer");
page->Bool("MIPSTracer enabled", [] { return mipsTracer.tracing_enabled; }, &SetMIPSTracerEnabled).EnabledFunc([]() {
bool temp = g_Config.iCpuCore == static_cast<int>(CPUCore::IR_INTERPRETER) && PSP_IsInited();
return temp && Core_IsStepping() && coreState != CORE_POWERDOWN;
});
page->Choice("Select the file path for the trace").OnClick([host](UI::EventParams &e) {
OnMIPSTracerPathChanged(host);
}).EnabledFunc([]() {
if (!PSP_IsInited())
return false;
return true;
});
page->Info("Current log file", mipsTracer.get_logging_path());
page->IntSlider(&mipsTracer.in_storage_capacity, 0x4'0000, 0x40'0000, "Storage capacity").Default(0x10'0000).Step(0x10000).Format("0x%x asm opcodes").OnChange([](UI::EventParams &) {
INFO_LOG(Log::JIT, "User changed the tracer's storage capacity to 0x%x", mipsTracer.in_storage_capacity);
});
page->IntSlider(&mipsTracer.in_max_trace_size, 0x1'0000, 0x40'0000, "Max allowed trace size").Default(0x10'0000).Step(0x10000).Format("%d basic blocks").OnChange([](UI::EventParams &) {
INFO_LOG(Log::JIT, "User changed the tracer's max trace size to %d", mipsTracer.in_max_trace_size);
});
page->Header("MIPSTracer actions");
page->Choice("Flush the trace").OnClick(&OnMIPSTracerFlushTrace);
page->Choice("Clear the JIT cache").OnClick(&OnMIPSTracerClearJitCache);
page->Choice("Clear the MIPSTracer").OnClick(&OnMIPSTracerClearTracer);
}
#endif
static void BuildAudioPage(SettingsPage *page) {
page->Bool(&g_Config.bForceFfmpegForAudioDec, "Use FFMPEG for all compressed audio");
}
static void BuildUIPage(SettingsPage *page) {
using namespace UI;
SettingsPageHost *host = page->Host();
UIContext *uiContext = host->PageScreenManager()->getUIContext();
page->Choice("Reload UI atlas").OnClick([uiContext](UI::EventParams &) {
uiContext->InvalidateAtlas();
});
// TODO: Move most of these strings out of the SysInfo category.
const I18NCat si = I18NCat::SYSINFO;
page->Header({ si, "Icon cache" });
IconCacheStats iconStats = g_iconCache.GetStats();
page->Info({ si, "Image data count" }, StringFromFormat("%d", iconStats.cachedCount));
page->Info({ si, "Texture count" }, StringFromFormat("%d", iconStats.textureCount));
page->Info({ si, "Data size" }, NiceSizeFormat(iconStats.dataSize));
page->Choice({ I18NCat::DIALOG, "Clear" }).OnClick([host](UI::EventParams &) {
g_iconCache.ClearData();
host->RecreatePageViews();
});
page->Header({ si, "Font cache" });
const TextDrawer *text = host->PageScreenManager()->getUIContext()->Text();
if (text) {
page->Info({ si, "Texture count" }, StringFromFormat("%d", text->GetStringCacheSize()));
page->Info({ si, "Data size" }, NiceSizeFormat(text->GetCacheDataSize()));
}
page->Header({ si, "Slider test" });
page->Custom({ si, "Slider test" }, [](UI::ViewGroup *parent) -> UI::View * {
static int testSliderValue = 0;
return parent->Add(new Slider(&testSliderValue, 0, 100, 1, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)));
});
page->Header({ si, "Notification tests" });
page->Choice({ si, "Error" }).OnClick([](UI::EventParams &) {
std::string str = "Error " + CodepointToUTF8(0x1F41B) + CodepointToUTF8(0x1F41C) + CodepointToUTF8(0x1F914);
g_OSD.Show(OSDType::MESSAGE_ERROR, str);
});
page->Choice({ si, "Warning" }).OnClick([](UI::EventParams &) {
g_OSD.Show(OSDType::MESSAGE_WARNING, "Warning, a pretty long warning heading", "Some\nAdditional\nDetail, some of which is very, very long and wide and will need line wrapping on most screens.");
});
page->Choice({ si, "Info" }).OnClick([](UI::EventParams &) {
g_OSD.Show(OSDType::MESSAGE_INFO, "Info, info info info info info info info info info info");
});
// This one is clickable
page->Choice({ si, "Success" }).OnClick([](UI::EventParams &) {
g_OSD.Show(OSDType::MESSAGE_SUCCESS, "Success", 0.0f, "clickable");
g_OSD.SetClickCallback("clickable", []() {
System_LaunchUrl(LaunchUrlType::BROWSER_URL, "https://www.google.com/");
});
});
page->Choice({ I18NCat::SYSTEM, "RetroAchievements" }).OnClick([](UI::EventParams &) {
g_OSD.Show(OSDType::MESSAGE_WARNING, "RetroAchievements warning", "", "I_RETROACHIEVEMENTS_LOGO");
});
page->Header({ si, "Progress tests" });
page->Choice({ si, "30%" }).OnClick([](UI::EventParams &) {
g_OSD.SetProgressBar("testprogress", "Test Progress", 1, 100, 30, 0.0f);
});
page->Choice({ si, "100%" }).OnClick([](UI::EventParams &) {
g_OSD.SetProgressBar("testprogress", "Test Progress", 1, 100, 100, 1.0f);
});
page->Choice({ si, "N/A%" }).OnClick([](UI::EventParams &) {
g_OSD.SetProgressBar("testprogress", "Test Progress", 0, 0, 0, 0.0f);
});
page->Choice({ si, "Success" }).OnClick([](UI::EventParams &) {
g_OSD.RemoveProgressBar("testprogress", true, 0.5f);
});
page->Choice({ si, "Failure" }).OnClick([](UI::EventParams &) {
g_OSD.RemoveProgressBar("testprogress", false, 0.5f);
});
page->Header({ si, "Achievement tests" });
page->Choice({ si, "Leaderboard tracker: Show" }).OnClick([](UI::EventParams &) {
g_OSD.ShowLeaderboardTracker(1, "My leaderboard tracker", true);
});
page->Choice({ si, "Leaderboard tracker: Update" }).OnClick([](UI::EventParams &) {
g_OSD.ShowLeaderboardTracker(1, "Updated tracker", true);
});
page->Choice({ si, "Leaderboard tracker: Hide" }).OnClick([](UI::EventParams &) {
g_OSD.ShowLeaderboardTracker(1, "", false);
});
page->Header({ I18NCat::ACHIEVEMENTS, "Notifications" });
page->Setting(&g_Config.iNotificationPos);
page->Setting(&g_Config.iAchievementsLeaderboardTrackerPos);
#ifdef _DEBUG
page->Bool({ I18NCat::ACHIEVEMENTS, "Pretend to be in-game (for testing)" }, [] {
return g_OSD.PretendIngame();
}, [](bool pretend) {
g_OSD.SetPretendIngame(pretend);
});
// Untranslated string because this is debug mode only, only for PPSSPP developers.
page->Header({ I18NCat::ACHIEVEMENTS, "Assert" });
page->Choice(SettingText::Raw("Assert")).OnClick([](UI::EventParams &) {
_dbg_assert_msg_(false, "Test assert message");
});
#endif
#if PPSSPP_PLATFORM(ANDROID)
page->Choice({ si, "Exception" }).OnClick([](UI::EventParams &) {
System_Notify(SystemNotification::TEST_JAVA_EXCEPTION);
});
#endif
}
static void BuildNetworkPage(SettingsPage *page) {
page->Header({ I18NCat::MAINSETTINGS, "Networking" });
page->Setting(&g_Config.bDontDownloadInfraJson);
// This is shared between RemoteISO and the remote debugger.
page->Setting(&g_Config.iRemoteISOPort);
page->Header({ I18NCat::NETWORKING, "AdHoc server" });
page->Setting(&g_Config.bAdhocServerShowPlayerPorts);
}
// TODO: Make this generic
extern int DefaultDepthRaster();
static void BuildGraphicsPage(SettingsPage *page) {
using namespace UI;
SettingsPageHost *host = page->Host();
Draw::DrawContext *draw = host->PageScreenManager()->getDrawContext();
page->Header({ I18NCat::MAINSETTINGS, "General" });
page->Setting(&g_Config.bVendorBugChecksEnabled);
page->Setting(&g_Config.bShaderCache);
page->Setting(&g_Config.bHardwareTransform);
page->Setting(&g_Config.iDisplayRefreshRate);
page->Header({ I18NCat::GRAPHICS, "Vulkan" });
page->Setting(&g_Config.bVulkanDisableImplicitLayers);
if (g_Config.iGPUBackend == (int)GPUBackend::VULKAN) {
page->Setting(&g_Config.bRenderMultiThreading).OnChange([](UI::EventParams &e) {
// TODO: Not translating yet. Will combine with other translations of settings that need restart.
g_OSD.Show(OSDType::MESSAGE_WARNING, "Restart required");
});
}
if (GetGPUBackend() == GPUBackend::VULKAN && SupportsCustomDriver()) {
page->Choice({ I18NCat::GRAPHICS, "AdrenoTools driver manager" }).OnClick([host](UI::EventParams &e) {
host->PageScreenManager()->push(new DriverManagerScreen(host->PageGamePath()));
});
}
page->Setting(&g_Config.iDepthRasterMode).Disabled(&g_Config.bSoftwareRendering).Setup([](View *view) {
PopupMultiChoice *depthRasterMode = static_cast<PopupMultiChoice *>(view);
depthRasterMode->SetDefault(DefaultDepthRaster());
depthRasterMode->SetChoiceIcon(3, ImageID("I_WARNING")); // It's a performance trap.
});
page->Header("Ubershaders");
if (draw->GetShaderLanguageDesc().bitwiseOps && !draw->GetBugs().Has(Draw::Bugs::UNIFORM_INDEXING_BROKEN)) {
// If the above if fails, the checkbox is redundant since it'll be force disabled anyway.
page->Setting(&g_Config.bUberShaderVertex);
}
// Experimental, allow some VR features without OpenXR
if (GetGPUBackend() == GPUBackend::OPENGL) {
page->Header({ I18NCat::VR, "Virtual reality" });
page->Setting(&g_Config.bForceVR);
}
// Experimental, will move to main graphics settings later.
bool multiViewSupported = draw->GetDeviceCaps().multiViewSupported;
auto enableStereo = [=]() -> bool {
return g_Config.bStereoRendering && multiViewSupported;
};
if (multiViewSupported) {
page->Header({ I18NCat::GRAPHICS, "Stereo rendering" });
page->Setting(&g_Config.bStereoRendering);
page->Custom({ I18NCat::GRAPHICS, "Stereo display shader" }, [host](UI::ViewGroup *parent) -> UI::View * {
ChoiceWithValueDisplay *stereoShaderChoice = parent->Add(new ChoiceWithValueDisplay(&g_Config.sStereoToMonoShader, T(I18NCat::GRAPHICS, "Stereo display shader"), &PostShaderTranslateName));
stereoShaderChoice->OnClick.Add([host](EventParams &e) {
auto gr = GetI18NCategory(I18NCat::GRAPHICS);
auto procScreen = new PostProcScreen(gr->T("Stereo display shader"), 0, true);
procScreen->OnChoice.Add([host](EventParams &) {
host->RecreatePageViews();
});
if (e.v)
procScreen->SetPopupOrigin(e.v);
host->PageScreenManager()->push(procScreen);
});
return stereoShaderChoice;
}).EnabledFunc(enableStereo);
const ShaderInfo *shaderInfo = GetPostShaderInfo(g_Config.sStereoToMonoShader);
if (shaderInfo) {
for (size_t i = 0; i < ARRAY_SIZE(shaderInfo->settings); ++i) {
auto &setting = shaderInfo->settings[i];
if (setting.name.empty()) {
continue;
}
// The value is looked up (and added if missing) when the slider is created, not when the page is built.
page->Custom({ I18NCat::POSTSHADERS, setting.name.c_str() }, [host, shaderInfo, i](UI::ViewGroup *parent) -> UI::View * {
auto &setting = shaderInfo->settings[i];
std::string key = StringFromFormat("%sSettingCurrentValue%d", shaderInfo->section.c_str(), i + 1);
bool keyExisted = g_Config.mPostShaderSetting.find(key) != g_Config.mPostShaderSetting.end();
auto &value = g_Config.mPostShaderSetting[key];
if (!keyExisted)
value = setting.value;
return parent->Add(new PopupSliderChoiceFloat(&value, setting.minValue, setting.maxValue, setting.value, T(I18NCat::POSTSHADERS, setting.name), setting.step, host->PageScreenManager()));
}).EnabledFunc([=] {
return !g_Config.bSkipBufferEffects && enableStereo();
});
}
}
}
}
static void CreateCrashHistoryList(UI::ViewGroup *crashHistory) {
using namespace UI;
auto di = GetI18NCategory(I18NCat::DIALOG);
std::vector<std::string> reports = Android_GetNativeCrashHistory(20);
if (reports.empty()) {
crashHistory->Add(new TextView(di->T("None")));
return;
}
for (size_t i = 0; i < reports.size(); i++) {
std::string name = StringFromFormat("Crash %d", (int)i);
CollapsibleSection *section = crashHistory->Add(new CollapsibleSection(name));
const std::string report = reports[i];
if (report.size() > 150) {
section->Add(new Choice(di->T("Copy to clipboard"), ImageID("I_FILE_COPY")))->OnClick.Add([report](UI::EventParams&) {
System_CopyStringToClipboard(report);
});
}
section->Add(new TextView(report, FLAG_WRAP_TEXT | FLAG_DYNAMIC_ASCII, true));
}
}
static void BuildCrashHistoryPage(SettingsPage *page) {
// A list of reports rather than settings.
page->Custom({ I18NCat::DEVELOPER, "Crash history" }, [](UI::ViewGroup *parent) -> UI::View * {
CreateCrashHistoryList(parent);
return parent->GetViewByIndex(parent->GetNumSubviews() - 1);
});
}
void BuildDeveloperToolsPage(SettingsPage *page) {
auto link = [page](SettingText title, void (*build)(SettingsPage *)) {
page->Link(title, I18NCat::DEVELOPER, build);
};
link({ I18NCat::MAINSETTINGS, "General" }, &BuildGeneralPage);
link({ I18NCat::DEVELOPER, "Texture Replacement" }, &BuildTextureReplacementPage);
link({ I18NCat::MAINSETTINGS, "Graphics" }, &BuildGraphicsPage);
link({ I18NCat::MAINSETTINGS, "Networking" }, &BuildNetworkPage);
link({ I18NCat::MAINSETTINGS, "Audio" }, &BuildAudioPage);
link({ I18NCat::DEVELOPER, "Tests" }, &BuildTestsPage);
link({ I18NCat::SYSTEM, "UI" }, &BuildUIPage);
link({ I18NCat::DEVELOPER, "Dump files" }, &BuildDumpFilePage);
link({ I18NCat::DEVELOPER, "HLE" }, &BuildHLEPage);
#if !PPSSPP_PLATFORM(ANDROID) && !PPSSPP_PLATFORM(IOS) && !PPSSPP_PLATFORM(SWITCH)
link({ I18NCat::DEVELOPER, "MIPSTracer" }, &BuildMIPSTracerPage);
#endif
if (System_GetPropertyInt(SYSPROP_SYSTEMVERSION) >= 30) {
link({ I18NCat::DEVELOPER, "Crash history" }, &BuildCrashHistoryPage);
}
}
static void OnLoggingChanged(UI::EventParams &e) {
System_Notify(SystemNotification::TOGGLE_DEBUG_CONSOLE);
}
static void OpenTexturesIniFile(HasIni *hasIni) {
std::string gameID = g_paramSFO.GetDiscID();
Path generatedFilename;
if (TextureReplacer::GenerateIni(gameID, generatedFilename)) {
if (System_GetPropertyBool(SYSPROP_SUPPORTS_OPEN_FILE_IN_EDITOR)) {
File::OpenFileInEditor(generatedFilename);
} else {
// Can't do much here, let's send a "toast" so the user sees that something happened.
auto dev = GetI18NCategory(I18NCat::DEVELOPER);
System_Toast((GetFriendlyPath(generatedFilename) + ": " + dev->T_cstr("Texture ini file created")).c_str());
}
*hasIni = HasIni::YES;
}
}
static void OnJitDebugTools(SettingsPageHost *host) {
host->PageScreenManager()->push(new JitDebugScreen());
}
static void OnGPUDriverTest(SettingsPageHost *host) {
host->PageScreenManager()->push(new GPUDriverTestScreen());
}
static void OnJitAffectingSetting(UI::EventParams &e) {
System_PostUIMessage(UIMessage::REQUEST_CLEAR_JIT);
}
#if PPSSPP_PLATFORM(IOS)
static void OnCopyStatesToRoot(UI::EventParams &e) {
Path savestate_dir = GetSysDirectory(DIRECTORY_SAVESTATE);
Path root_dir = GetSysDirectory(DIRECTORY_MEMSTICK_ROOT);
std::vector<File::FileInfo> files;
GetFilesInDir(savestate_dir, &files, nullptr, 0);
for (const File::FileInfo &file : files) {
Path src = file.fullName;
Path dst = root_dir / file.name;
INFO_LOG(Log::System, "Copying file '%s' to '%s'", src.c_str(), dst.c_str());
File::Copy(src, dst);
}
}
#endif
static void SetRemoteDebuggerAllowed(bool allow) {
if (allow) {
// Only needed for connecting from another device, but that's the usual case.
System_RequestLocalNetworkPermission();
StartWebServer(WebServerFlags::DEBUGGER);
} else {
StopWebServer(WebServerFlags::DEBUGGER);
}
// Persist the setting. Maybe should separate?
g_Config.bRemoteDebuggerOnStartup = allow;
}
#if !PPSSPP_PLATFORM(ANDROID) && !PPSSPP_PLATFORM(IOS) && !PPSSPP_PLATFORM(SWITCH)
static void SetMIPSTracerEnabled(bool enabled) {
if (enabled) {
u32 capacity = mipsTracer.in_storage_capacity;
u32 trace_size = mipsTracer.in_max_trace_size;
mipsTracer.initialize(capacity, trace_size);
mipsTracer.start_tracing();
} else {
mipsTracer.stop_tracing();
}
}
static void OnMIPSTracerPathChanged(SettingsPageHost *host) {
auto dev = GetI18NCategory(I18NCat::DEVELOPER);
System_BrowseForFileSave(
host->PageRequesterToken(),
dev->T("Select the log file"),
"trace.txt",
BrowseFileType::ANY,
[host](std::string_view value, int) {
mipsTracer.set_logging_path(std::string(value));
// The views may have been recreated while the dialog was open, so don't keep a pointer to the path view.
host->RecreatePageViews();
}
);
}
static void OnMIPSTracerFlushTrace(UI::EventParams &e) {
mipsTracer.flush_to_file();
// The error logs are emitted inside the tracer
}
static void OnMIPSTracerClearJitCache(UI::EventParams &e) {
INFO_LOG(Log::JIT, "Clearing the jit cache...");
System_PostUIMessage(UIMessage::REQUEST_CLEAR_JIT);
}
static void OnMIPSTracerClearTracer(UI::EventParams &e) {
INFO_LOG(Log::JIT, "Clearing the MIPSTracer...");
mipsTracer.clear();
}
#endif