1 // Copyright (c) 2013- PPSSPP Project.
2 
3 // This program is free software: you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation, version 2.0 or later versions.
6 
7 // This program is distributed in the hope that it will be useful,
8 // but WITHOUT ANY WARRANTY; without even the implied warranty of
9 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10 // GNU General Public License 2.0 for more details.
11 
12 // A copy of the GPL 2.0 should have been included with the program.
13 // If not, see http://www.gnu.org/licenses/
14 
15 // Official git repository and contact information can be found at
16 // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
17 
18 #include "ppsspp_config.h"
19 
20 #include <algorithm>
21 #include <set>
22 
23 #include "Common/Net/Resolve.h"
24 #include "Common/GPU/OpenGL/GLFeatures.h"
25 #include "Common/Render/DrawBuffer.h"
26 #include "Common/UI/Root.h"
27 #include "Common/UI/View.h"
28 #include "Common/UI/ViewGroup.h"
29 #include "Common/UI/Context.h"
30 
31 #include "Common/System/Display.h"  // Only to check screen aspect ratio with pixel_yres/pixel_xres
32 #include "Common/System/System.h"
33 #include "Common/System/NativeApp.h"
34 #include "Common/Data/Color/RGBAUtil.h"
35 #include "Common/Math/curves.h"
36 #include "Common/Data/Text/I18n.h"
37 #include "Common/Data/Encoding/Utf8.h"
38 #include "UI/EmuScreen.h"
39 #include "UI/GameSettingsScreen.h"
40 #include "UI/GameInfoCache.h"
41 #include "UI/GamepadEmu.h"
42 #include "UI/MiscScreens.h"
43 #include "UI/ControlMappingScreen.h"
44 #include "UI/DevScreens.h"
45 #include "UI/DisplayLayoutScreen.h"
46 #include "UI/RemoteISOScreen.h"
47 #include "UI/SavedataScreen.h"
48 #include "UI/TouchControlLayoutScreen.h"
49 #include "UI/TouchControlVisibilityScreen.h"
50 #include "UI/TiltAnalogSettingsScreen.h"
51 #include "UI/TiltEventProcessor.h"
52 #include "UI/GPUDriverTestScreen.h"
53 #include "UI/MemStickScreen.h"
54 
55 #include "Common/File/FileUtil.h"
56 #include "Common/OSVersion.h"
57 #include "Common/TimeUtil.h"
58 #include "Common/StringUtils.h"
59 #include "Core/Config.h"
60 #include "Core/ConfigValues.h"
61 #include "Core/Host.h"
62 #include "Core/KeyMap.h"
63 #include "Core/Instance.h"
64 #include "Core/System.h"
65 #include "Core/Reporting.h"
66 #include "Core/TextureReplacer.h"
67 #include "Core/WebServer.h"
68 #include "Core/HLE/sceUsbCam.h"
69 #include "Core/HLE/sceUsbMic.h"
70 #include "GPU/Common/PostShader.h"
71 #include "android/jni/TestRunner.h"
72 #include "GPU/GPUInterface.h"
73 #include "GPU/Common/FramebufferManagerCommon.h"
74 
75 #if defined(_WIN32) && !PPSSPP_PLATFORM(UWP)
76 #pragma warning(disable:4091)  // workaround bug in VS2015 headers
77 #include "Windows/MainWindow.h"
78 #include <shlobj.h>
79 #include "Windows/W32Util/ShellUtil.h"
80 #endif
81 
82 #if PPSSPP_PLATFORM(ANDROID)
83 
84 #include "android/jni/AndroidAudio.h"
85 #include "android/jni/AndroidContentURI.h"
86 
87 extern AndroidAudioState *g_audioState;
88 
89 #endif
90 
GameSettingsScreen(const Path & gamePath,std::string gameID,bool editThenRestore)91 GameSettingsScreen::GameSettingsScreen(const Path &gamePath, std::string gameID, bool editThenRestore)
92 	: UIDialogScreenWithGameBackground(gamePath), gameID_(gameID), editThenRestore_(editThenRestore) {
93 	lastVertical_ = UseVerticalLayout();
94 	prevInflightFrames_ = g_Config.iInflightFrames;
95 }
96 
UseVerticalLayout() const97 bool GameSettingsScreen::UseVerticalLayout() const {
98 	return dp_yres > dp_xres * 1.1f;
99 }
100 
101 // This needs before run CheckGPUFeatures()
102 // TODO: Remove this if fix the issue
CheckSupportShaderTessellationGLES()103 bool CheckSupportShaderTessellationGLES() {
104 #if PPSSPP_PLATFORM(UWP)
105 	return true;
106 #else
107 	// TODO: Make work with non-GL backends
108 	int maxVertexTextureImageUnits = gl_extensions.maxVertexTextureUnits;
109 	bool vertexTexture = maxVertexTextureImageUnits >= 3; // At least 3 for hardware tessellation
110 
111 	bool textureFloat = gl_extensions.ARB_texture_float || gl_extensions.OES_texture_float;
112 	bool hasTexelFetch = gl_extensions.GLES3 || (!gl_extensions.IsGLES && gl_extensions.VersionGEThan(3, 3, 0)) || gl_extensions.EXT_gpu_shader4;
113 
114 	return vertexTexture && textureFloat && hasTexelFetch;
115 #endif
116 }
117 
DoesBackendSupportHWTess()118 bool DoesBackendSupportHWTess() {
119 	switch (GetGPUBackend()) {
120 	case GPUBackend::OPENGL:
121 		return CheckSupportShaderTessellationGLES();
122 	case GPUBackend::VULKAN:
123 	case GPUBackend::DIRECT3D11:
124 		return true;
125 	default:
126 		return false;
127 	}
128 }
129 
UsingHardwareTextureScaling()130 static bool UsingHardwareTextureScaling() {
131 	// For now, Vulkan only.
132 	return g_Config.bTexHardwareScaling && GetGPUBackend() == GPUBackend::VULKAN && !g_Config.bSoftwareRendering;
133 }
134 
TextureTranslateName(const char * value)135 static std::string TextureTranslateName(const char *value) {
136 	auto ps = GetI18NCategory("TextureShaders");
137 	const TextureShaderInfo *info = GetTextureShaderInfo(value);
138 	if (info) {
139 		return ps->T(value, info ? info->name.c_str() : value);
140 	} else {
141 		return value;
142 	}
143 }
144 
PostShaderTranslateName(const char * value)145 static std::string PostShaderTranslateName(const char *value) {
146 	auto ps = GetI18NCategory("PostShaders");
147 	const ShaderInfo *info = GetPostShaderInfo(value);
148 	if (info) {
149 		return ps->T(value, info ? info->name.c_str() : value);
150 	} else {
151 		return value;
152 	}
153 }
154 
GPUDeviceNameSetting()155 static std::string *GPUDeviceNameSetting() {
156 	if (g_Config.iGPUBackend == (int)GPUBackend::VULKAN) {
157 		return &g_Config.sVulkanDevice;
158 	}
159 #ifdef _WIN32
160 	if (g_Config.iGPUBackend == (int)GPUBackend::DIRECT3D11) {
161 		return &g_Config.sD3D11Device;
162 	}
163 #endif
164 	return nullptr;
165 }
166 
PathToVisualUsbPath(Path path,std::string & outPath)167 bool PathToVisualUsbPath(Path path, std::string &outPath) {
168 	switch (path.Type()) {
169 	case PathType::NATIVE:
170 		if (path.StartsWith(g_Config.memStickDirectory)) {
171 			return g_Config.memStickDirectory.ComputePathTo(path, outPath);
172 		}
173 		break;
174 	case PathType::CONTENT_URI:
175 #if PPSSPP_PLATFORM(ANDROID)
176 	{
177 		// Try to parse something sensible out of the content URI.
178 		AndroidContentURI uri(path.ToString());
179 		outPath = uri.RootPath();
180 		if (startsWith(outPath, "primary:")) {
181 			outPath = "/" + outPath.substr(8);
182 		}
183 		return true;
184 	}
185 #endif
186 	default:
187 		break;
188 	}
189 	return false;
190 }
191 
CreateViews()192 void GameSettingsScreen::CreateViews() {
193 	ReloadAllPostShaderInfo(screenManager()->getDrawContext());
194 
195 	if (editThenRestore_) {
196 		std::shared_ptr<GameInfo> info = g_gameInfoCache->GetInfo(nullptr, gamePath_, 0);
197 		g_Config.loadGameConfig(gameID_, info->GetTitle());
198 	}
199 
200 	iAlternateSpeedPercent1_ = g_Config.iFpsLimit1 < 0 ? -1 : (g_Config.iFpsLimit1 * 100) / 60;
201 	iAlternateSpeedPercent2_ = g_Config.iFpsLimit2 < 0 ? -1 : (g_Config.iFpsLimit2 * 100) / 60;
202 
203 	bool vertical = UseVerticalLayout();
204 
205 	// Information in the top left.
206 	// Back button to the bottom left.
207 	// Scrolling action menu to the right.
208 	using namespace UI;
209 
210 	auto di = GetI18NCategory("Dialog");
211 	auto gr = GetI18NCategory("Graphics");
212 	auto co = GetI18NCategory("Controls");
213 	auto a = GetI18NCategory("Audio");
214 	auto sa = GetI18NCategory("Savedata");
215 	auto sy = GetI18NCategory("System");
216 	auto n = GetI18NCategory("Networking");
217 	auto ms = GetI18NCategory("MainSettings");
218 	auto dev = GetI18NCategory("Developer");
219 	auto ri = GetI18NCategory("RemoteISO");
220 	auto ps = GetI18NCategory("PostShaders");
221 
222 	root_ = new AnchorLayout(new LayoutParams(FILL_PARENT, FILL_PARENT));
223 
224 	TabHolder *tabHolder;
225 	if (vertical) {
226 		LinearLayout *verticalLayout = new LinearLayout(ORIENT_VERTICAL, new LayoutParams(FILL_PARENT, FILL_PARENT));
227 		tabHolder = new TabHolder(ORIENT_HORIZONTAL, 200, new LinearLayoutParams(1.0f));
228 		verticalLayout->Add(tabHolder);
229 		verticalLayout->Add(new Choice(di->T("Back"), "", false, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, 0.0f, Margins(0))))->OnClick.Handle<UIScreen>(this, &UIScreen::OnBack);
230 		root_->Add(verticalLayout);
231 	} else {
232 		tabHolder = new TabHolder(ORIENT_VERTICAL, 200, new AnchorLayoutParams(10, 0, 10, 0, false));
233 		root_->Add(tabHolder);
234 		AddStandardBack(root_);
235 	}
236 	tabHolder->SetTag("GameSettings");
237 	root_->SetDefaultFocusView(tabHolder);
238 
239 	float leftSide = 40.0f;
240 	if (!vertical) {
241 		leftSide += 200.0f;
242 	}
243 	settingInfo_ = new SettingInfoMessage(ALIGN_CENTER | FLAG_WRAP_TEXT, new AnchorLayoutParams(dp_xres - leftSide - 40.0f, WRAP_CONTENT, leftSide, dp_yres - 80.0f - 40.0f, NONE, NONE));
244 	settingInfo_->SetBottomCutoff(dp_yres - 200.0f);
245 	root_->Add(settingInfo_);
246 
247 	// Show it again if we recreated the view
248 	if (oldSettingInfo_ != "") {
249 		settingInfo_->Show(oldSettingInfo_, nullptr);
250 	}
251 
252 	// TODO: These currently point to global settings, not game specific ones.
253 
254 	// Graphics
255 	ViewGroup *graphicsSettingsScroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, FILL_PARENT));
256 	graphicsSettingsScroll->SetTag("GameSettingsGraphics");
257 	LinearLayout *graphicsSettings = new LinearLayoutList(ORIENT_VERTICAL);
258 	graphicsSettings->SetSpacing(0);
259 	graphicsSettingsScroll->Add(graphicsSettings);
260 	tabHolder->AddTab(ms->T("Graphics"), graphicsSettingsScroll);
261 
262 	graphicsSettings->Add(new ItemHeader(gr->T("Rendering Mode")));
263 
264 #if !PPSSPP_PLATFORM(UWP)
265 	static const char *renderingBackend[] = { "OpenGL", "Direct3D 9", "Direct3D 11", "Vulkan" };
266 	PopupMultiChoice *renderingBackendChoice = graphicsSettings->Add(new PopupMultiChoice(&g_Config.iGPUBackend, gr->T("Backend"), renderingBackend, (int)GPUBackend::OPENGL, ARRAY_SIZE(renderingBackend), gr->GetName(), screenManager()));
267 	renderingBackendChoice->OnChoice.Handle(this, &GameSettingsScreen::OnRenderingBackend);
268 
269 	if (!g_Config.IsBackendEnabled(GPUBackend::OPENGL))
270 		renderingBackendChoice->HideChoice((int)GPUBackend::OPENGL);
271 	if (!g_Config.IsBackendEnabled(GPUBackend::DIRECT3D9))
272 		renderingBackendChoice->HideChoice((int)GPUBackend::DIRECT3D9);
273 	if (!g_Config.IsBackendEnabled(GPUBackend::DIRECT3D11))
274 		renderingBackendChoice->HideChoice((int)GPUBackend::DIRECT3D11);
275 	if (!g_Config.IsBackendEnabled(GPUBackend::VULKAN))
276 		renderingBackendChoice->HideChoice((int)GPUBackend::VULKAN);
277 
278 	if (!IsFirstInstance()) {
279 		// If we're not the first instance, can't save the setting, and it requires a restart, so...
280 		renderingBackendChoice->SetEnabled(false);
281 	}
282 #endif
283 
284 	Draw::DrawContext *draw = screenManager()->getDrawContext();
285 
286 	// Backends that don't allow a device choice will only expose one device.
287 	if (draw->GetDeviceList().size() > 1) {
288 		std::string *deviceNameSetting = GPUDeviceNameSetting();
289 		if (deviceNameSetting) {
290 			PopupMultiChoiceDynamic *deviceChoice = graphicsSettings->Add(new PopupMultiChoiceDynamic(deviceNameSetting, gr->T("Device"), draw->GetDeviceList(), nullptr, screenManager()));
291 			deviceChoice->OnChoice.Handle(this, &GameSettingsScreen::OnRenderingDevice);
292 		}
293 	}
294 
295 	static const char *renderingMode[] = { "Non-Buffered Rendering", "Buffered Rendering" };
296 	PopupMultiChoice *renderingModeChoice = graphicsSettings->Add(new PopupMultiChoice(&g_Config.iRenderingMode, gr->T("Mode"), renderingMode, 0, ARRAY_SIZE(renderingMode), gr->GetName(), screenManager()));
297 	renderingModeChoice->OnChoice.Add([=](EventParams &e) {
298 		switch (g_Config.iRenderingMode) {
299 		case FB_NON_BUFFERED_MODE:
300 			settingInfo_->Show(gr->T("RenderingMode NonBuffered Tip", "Faster, but graphics may be missing in some games"), e.v);
301 			break;
302 		case FB_BUFFERED_MODE:
303 			break;
304 		}
305 		return UI::EVENT_CONTINUE;
306 	});
307 	renderingModeChoice->OnChoice.Handle(this, &GameSettingsScreen::OnRenderingMode);
308 	renderingModeChoice->SetDisabledPtr(&g_Config.bSoftwareRendering);
309 	CheckBox *blockTransfer = graphicsSettings->Add(new CheckBox(&g_Config.bBlockTransferGPU, gr->T("Simulate Block Transfer", "Simulate Block Transfer")));
310 	blockTransfer->OnClick.Add([=](EventParams &e) {
311 		if (!g_Config.bBlockTransferGPU)
312 			settingInfo_->Show(gr->T("BlockTransfer Tip", "Some games require this to be On for correct graphics"), e.v);
313 		return UI::EVENT_CONTINUE;
314 	});
315 	blockTransfer->SetDisabledPtr(&g_Config.bSoftwareRendering);
316 
317 	bool showSoftGPU = true;
318 #ifdef MOBILE_DEVICE
319 	// On Android, only show the software rendering setting if it's already enabled.
320 	// Can still be turned on through INI file editing.
321 	showSoftGPU = g_Config.bSoftwareRendering;
322 #endif
323 	if (showSoftGPU) {
324 		CheckBox *softwareGPU = graphicsSettings->Add(new CheckBox(&g_Config.bSoftwareRendering, gr->T("Software Rendering", "Software Rendering (slow)")));
325 		softwareGPU->OnClick.Add([=](EventParams &e) {
326 			if (g_Config.bSoftwareRendering)
327 				settingInfo_->Show(gr->T("SoftGPU Tip", "Currently VERY slow"), e.v);
328 			return UI::EVENT_CONTINUE;
329 		});
330 		softwareGPU->OnClick.Handle(this, &GameSettingsScreen::OnSoftwareRendering);
331 		softwareGPU->SetEnabled(!PSP_IsInited());
332 	}
333 
334 	graphicsSettings->Add(new ItemHeader(gr->T("Frame Rate Control")));
335 	static const char *frameSkip[] = {"Off", "1", "2", "3", "4", "5", "6", "7", "8"};
336 	graphicsSettings->Add(new PopupMultiChoice(&g_Config.iFrameSkip, gr->T("Frame Skipping"), frameSkip, 0, ARRAY_SIZE(frameSkip), gr->GetName(), screenManager()));
337 	static const char *frameSkipType[] = {"Number of Frames", "Percent of FPS"};
338 	graphicsSettings->Add(new PopupMultiChoice(&g_Config.iFrameSkipType, gr->T("Frame Skipping Type"), frameSkipType, 0, ARRAY_SIZE(frameSkipType), gr->GetName(), screenManager()));
339 	frameSkipAuto_ = graphicsSettings->Add(new CheckBox(&g_Config.bAutoFrameSkip, gr->T("Auto FrameSkip")));
340 	frameSkipAuto_->OnClick.Handle(this, &GameSettingsScreen::OnAutoFrameskip);
341 
342 	PopupSliderChoice *altSpeed1 = graphicsSettings->Add(new PopupSliderChoice(&iAlternateSpeedPercent1_, 0, 1000, gr->T("Alternative Speed", "Alternative speed"), 5, screenManager(), gr->T("%, 0:unlimited")));
343 	altSpeed1->SetFormat("%i%%");
344 	altSpeed1->SetZeroLabel(gr->T("Unlimited"));
345 	altSpeed1->SetNegativeDisable(gr->T("Disabled"));
346 
347 	PopupSliderChoice *altSpeed2 = graphicsSettings->Add(new PopupSliderChoice(&iAlternateSpeedPercent2_, 0, 1000, gr->T("Alternative Speed 2", "Alternative speed 2 (in %, 0 = unlimited)"), 5, screenManager(), gr->T("%, 0:unlimited")));
348 	altSpeed2->SetFormat("%i%%");
349 	altSpeed2->SetZeroLabel(gr->T("Unlimited"));
350 	altSpeed2->SetNegativeDisable(gr->T("Disabled"));
351 
352 	graphicsSettings->Add(new ItemHeader(gr->T("Postprocessing effect")));
353 
354 	std::set<std::string> alreadyAddedShader;
355 	for (int i = 0; i < g_Config.vPostShaderNames.size() + 1 && i < ARRAY_SIZE(shaderNames_); ++i) {
356 		// Vector element pointer get invalidated on resize, cache name to have always a valid reference in the rendering thread
357 		shaderNames_[i] = i == g_Config.vPostShaderNames.size() ? "Off" : g_Config.vPostShaderNames[i];
358 		postProcChoice_ = graphicsSettings->Add(new ChoiceWithValueDisplay(&shaderNames_[i], StringFromFormat("%s #%d", gr->T("Postprocessing Shader"), i + 1), &PostShaderTranslateName));
359 		postProcChoice_->OnClick.Add([=](EventParams &e) {
360 			auto gr = GetI18NCategory("Graphics");
361 			auto procScreen = new PostProcScreen(gr->T("Postprocessing Shader"), i);
362 			procScreen->OnChoice.Handle(this, &GameSettingsScreen::OnPostProcShaderChange);
363 			if (e.v)
364 				procScreen->SetPopupOrigin(e.v);
365 			screenManager()->push(procScreen);
366 			return UI::EVENT_DONE;
367 		});
368 		postProcChoice_->SetEnabledFunc([] {
369 			return g_Config.iRenderingMode != FB_NON_BUFFERED_MODE;
370 		});
371 
372 		// No need for settings on the last one.
373 		if (i == g_Config.vPostShaderNames.size())
374 			continue;
375 
376 		auto shaderChain = GetPostShaderChain(g_Config.vPostShaderNames[i]);
377 		for (auto shaderInfo : shaderChain) {
378 			// Disable duplicated shader slider
379 			bool duplicated = alreadyAddedShader.find(shaderInfo->section) != alreadyAddedShader.end();
380 			alreadyAddedShader.insert(shaderInfo->section);
381 			for (size_t i = 0; i < ARRAY_SIZE(shaderInfo->settings); ++i) {
382 				auto &setting = shaderInfo->settings[i];
383 				if (!setting.name.empty()) {
384 					auto &value = g_Config.mPostShaderSetting[StringFromFormat("%sSettingValue%d", shaderInfo->section.c_str(), i + 1)];
385 					if (duplicated) {
386 						auto sliderName = StringFromFormat("%s %s", ps->T(setting.name), ps->T("(duplicated setting, previous slider will be used)"));
387 						PopupSliderChoiceFloat *settingValue = graphicsSettings->Add(new PopupSliderChoiceFloat(&value, setting.minValue, setting.maxValue, sliderName, setting.step, screenManager()));
388 						settingValue->SetEnabled(false);
389 					} else {
390 						PopupSliderChoiceFloat *settingValue = graphicsSettings->Add(new PopupSliderChoiceFloat(&value, setting.minValue, setting.maxValue, ps->T(setting.name), setting.step, screenManager()));
391 						settingValue->SetEnabledFunc([] {
392 							return g_Config.iRenderingMode != FB_NON_BUFFERED_MODE;
393 						});
394 					}
395 				}
396 			}
397 		}
398 	}
399 
400 	graphicsSettings->Add(new ItemHeader(gr->T("Screen layout")));
401 #if !defined(MOBILE_DEVICE)
402 	graphicsSettings->Add(new CheckBox(&g_Config.bFullScreen, gr->T("FullScreen", "Full Screen")))->OnClick.Handle(this, &GameSettingsScreen::OnFullscreenChange);
403 	if (System_GetPropertyInt(SYSPROP_DISPLAY_COUNT) > 1) {
404 		CheckBox *fullscreenMulti = new CheckBox(&g_Config.bFullScreenMulti, gr->T("Use all displays"));
405 		fullscreenMulti->SetEnabledPtr(&g_Config.bFullScreen);
406 		graphicsSettings->Add(fullscreenMulti)->OnClick.Handle(this, &GameSettingsScreen::OnFullscreenChange);
407 	}
408 #endif
409 	// Display Layout Editor: To avoid overlapping touch controls on large tablets, meet geeky demands for integer zoom/unstretched image etc.
410 	displayEditor_ = graphicsSettings->Add(new Choice(gr->T("Display layout editor")));
411 	displayEditor_->OnClick.Handle(this, &GameSettingsScreen::OnDisplayLayoutEditor);
412 
413 #if PPSSPP_PLATFORM(ANDROID)
414 	// Hide insets option if no insets, or OS too old.
415 	if (System_GetPropertyInt(SYSPROP_SYSTEMVERSION) >= 28 &&
416 		(System_GetPropertyFloat(SYSPROP_DISPLAY_SAFE_INSET_LEFT) != 0.0f ||
417 		 System_GetPropertyFloat(SYSPROP_DISPLAY_SAFE_INSET_TOP) != 0.0f ||
418 		 System_GetPropertyFloat(SYSPROP_DISPLAY_SAFE_INSET_RIGHT) != 0.0f ||
419 		 System_GetPropertyFloat(SYSPROP_DISPLAY_SAFE_INSET_BOTTOM) != 0.0f)) {
420 		graphicsSettings->Add(new CheckBox(&g_Config.bIgnoreScreenInsets, gr->T("Ignore camera notch when centering")));
421 	}
422 
423 	// Hide Immersive Mode on pre-kitkat Android
424 	if (System_GetPropertyInt(SYSPROP_SYSTEMVERSION) >= 19) {
425 		// Let's reuse the Fullscreen translation string from desktop.
426 		graphicsSettings->Add(new CheckBox(&g_Config.bImmersiveMode, gr->T("FullScreen", "Full Screen")))->OnClick.Handle(this, &GameSettingsScreen::OnImmersiveModeChange);
427 	}
428 #endif
429 
430 	graphicsSettings->Add(new ItemHeader(gr->T("Performance")));
431 	static const char *internalResolutions[] = { "Auto (1:1)", "1x PSP", "2x PSP", "3x PSP", "4x PSP", "5x PSP", "6x PSP", "7x PSP", "8x PSP", "9x PSP", "10x PSP" };
432 	resolutionChoice_ = graphicsSettings->Add(new PopupMultiChoice(&g_Config.iInternalResolution, gr->T("Rendering Resolution"), internalResolutions, 0, ARRAY_SIZE(internalResolutions), gr->GetName(), screenManager()));
433 	resolutionChoice_->OnChoice.Handle(this, &GameSettingsScreen::OnResolutionChange);
434 	resolutionChoice_->SetEnabledFunc([] {
435 		return !g_Config.bSoftwareRendering && g_Config.iRenderingMode != FB_NON_BUFFERED_MODE;
436 	});
437 
438 #if PPSSPP_PLATFORM(ANDROID)
439 	if (System_GetPropertyInt(SYSPROP_DEVICE_TYPE) != DEVICE_TYPE_TV) {
440 		static const char *deviceResolutions[] = { "Native device resolution", "Auto (same as Rendering)", "1x PSP", "2x PSP", "3x PSP", "4x PSP", "5x PSP" };
441 		int max_res_temp = std::max(System_GetPropertyInt(SYSPROP_DISPLAY_XRES), System_GetPropertyInt(SYSPROP_DISPLAY_YRES)) / 480 + 2;
442 		if (max_res_temp == 3)
443 			max_res_temp = 4;  // At least allow 2x
444 		int max_res = std::min(max_res_temp, (int)ARRAY_SIZE(deviceResolutions));
445 		UI::PopupMultiChoice *hwscale = graphicsSettings->Add(new PopupMultiChoice(&g_Config.iAndroidHwScale, gr->T("Display Resolution (HW scaler)"), deviceResolutions, 0, max_res, gr->GetName(), screenManager()));
446 		hwscale->OnChoice.Handle(this, &GameSettingsScreen::OnHwScaleChange);  // To refresh the display mode
447 	}
448 #endif
449 
450 #if !(PPSSPP_PLATFORM(ANDROID) || defined(USING_QT_UI) || PPSSPP_PLATFORM(UWP) || PPSSPP_PLATFORM(IOS))
451 	CheckBox *vSync = graphicsSettings->Add(new CheckBox(&g_Config.bVSync, gr->T("VSync")));
452 	vSync->OnClick.Add([=](EventParams &e) {
453 		NativeResized();
454 		return UI::EVENT_CONTINUE;
455 	});
456 #endif
457 
458 	CheckBox *frameDuplication = graphicsSettings->Add(new CheckBox(&g_Config.bRenderDuplicateFrames, gr->T("Render duplicate frames to 60hz")));
459 	frameDuplication->OnClick.Add([=](EventParams &e) {
460 		settingInfo_->Show(gr->T("RenderDuplicateFrames Tip", "Can make framerate smoother in games that run at lower framerates"), e.v);
461 		return UI::EVENT_CONTINUE;
462 	});
463 	frameDuplication->SetEnabledFunc([] {
464 		return g_Config.iRenderingMode != FB_NON_BUFFERED_MODE && g_Config.iFrameSkip == 0;
465 	});
466 
467 	if (GetGPUBackend() == GPUBackend::VULKAN || GetGPUBackend() == GPUBackend::OPENGL) {
468 		static const char *bufferOptions[] = { "No buffer", "Up to 1", "Up to 2" };
469 		PopupMultiChoice *inflightChoice = graphicsSettings->Add(new PopupMultiChoice(&g_Config.iInflightFrames, gr->T("Buffer graphics commands (faster, input lag)"), bufferOptions, 0, ARRAY_SIZE(bufferOptions), gr->GetName(), screenManager()));
470 		inflightChoice->OnChoice.Handle(this, &GameSettingsScreen::OnInflightFramesChoice);
471 	}
472 
473 	CheckBox *hwTransform = graphicsSettings->Add(new CheckBox(&g_Config.bHardwareTransform, gr->T("Hardware Transform")));
474 	hwTransform->OnClick.Handle(this, &GameSettingsScreen::OnHardwareTransform);
475 	hwTransform->SetDisabledPtr(&g_Config.bSoftwareRendering);
476 
477 	CheckBox *swSkin = graphicsSettings->Add(new CheckBox(&g_Config.bSoftwareSkinning, gr->T("Software Skinning")));
478 	swSkin->OnClick.Add([=](EventParams &e) {
479 		settingInfo_->Show(gr->T("SoftwareSkinning Tip", "Combine skinned model draws on the CPU, faster in most games"), e.v);
480 		return UI::EVENT_CONTINUE;
481 	});
482 	swSkin->SetDisabledPtr(&g_Config.bSoftwareRendering);
483 
484 	CheckBox *vtxCache = graphicsSettings->Add(new CheckBox(&g_Config.bVertexCache, gr->T("Vertex Cache")));
485 	vtxCache->OnClick.Add([=](EventParams &e) {
486 		settingInfo_->Show(gr->T("VertexCache Tip", "Faster, but may cause temporary flicker"), e.v);
487 		return UI::EVENT_CONTINUE;
488 	});
489 	vtxCache->SetEnabledFunc([] {
490 		return !g_Config.bSoftwareRendering && g_Config.bHardwareTransform;
491 	});
492 
493 	CheckBox *texBackoff = graphicsSettings->Add(new CheckBox(&g_Config.bTextureBackoffCache, gr->T("Lazy texture caching", "Lazy texture caching (speedup)")));
494 	texBackoff->SetDisabledPtr(&g_Config.bSoftwareRendering);
495 
496 	CheckBox *texSecondary_ = graphicsSettings->Add(new CheckBox(&g_Config.bTextureSecondaryCache, gr->T("Retain changed textures", "Retain changed textures (speedup, mem hog)")));
497 	texSecondary_->OnClick.Add([=](EventParams &e) {
498 		settingInfo_->Show(gr->T("RetainChangedTextures Tip", "Makes many games slower, but some games a lot faster"), e.v);
499 		return UI::EVENT_CONTINUE;
500 	});
501 	texSecondary_->SetDisabledPtr(&g_Config.bSoftwareRendering);
502 
503 	CheckBox *framebufferSlowEffects = graphicsSettings->Add(new CheckBox(&g_Config.bDisableSlowFramebufEffects, gr->T("Disable slower effects (speedup)")));
504 	framebufferSlowEffects->SetDisabledPtr(&g_Config.bSoftwareRendering);
505 
506 	// Seems solid, so we hide the setting.
507 	/*CheckBox *vtxJit = graphicsSettings->Add(new CheckBox(&g_Config.bVertexDecoderJit, gr->T("Vertex Decoder JIT")));
508 
509 	if (PSP_IsInited()) {
510 		vtxJit->SetEnabled(false);
511 	}*/
512 
513 	static const char *quality[] = { "Low", "Medium", "High" };
514 	PopupMultiChoice *beziersChoice = graphicsSettings->Add(new PopupMultiChoice(&g_Config.iSplineBezierQuality, gr->T("LowCurves", "Spline/Bezier curves quality"), quality, 0, ARRAY_SIZE(quality), gr->GetName(), screenManager()));
515 	beziersChoice->OnChoice.Add([=](EventParams &e) {
516 		if (g_Config.iSplineBezierQuality != 0) {
517 			settingInfo_->Show(gr->T("LowCurves Tip", "Only used by some games, controls smoothness of curves"), e.v);
518 		}
519 		return UI::EVENT_CONTINUE;
520 	});
521 
522 	CheckBox *tessellationHW = graphicsSettings->Add(new CheckBox(&g_Config.bHardwareTessellation, gr->T("Hardware Tessellation")));
523 	tessellationHW->OnClick.Add([=](EventParams &e) {
524 		settingInfo_->Show(gr->T("HardwareTessellation Tip", "Uses hardware to make curves"), e.v);
525 		return UI::EVENT_CONTINUE;
526 	});
527 	tessHWEnable_ = DoesBackendSupportHWTess() && !g_Config.bSoftwareRendering && g_Config.bHardwareTransform;
528 	tessellationHW->SetEnabledPtr(&tessHWEnable_);
529 
530 	// In case we're going to add few other antialiasing option like MSAA in the future.
531 	// graphicsSettings->Add(new CheckBox(&g_Config.bFXAA, gr->T("FXAA")));
532 	graphicsSettings->Add(new ItemHeader(gr->T("Texture Scaling")));
533 #ifndef MOBILE_DEVICE
534 	static const char *texScaleLevels[] = {"Off", "2x", "3x", "4x", "5x"};
535 #else
536 	static const char *texScaleLevels[] = {"Off", "2x", "3x"};
537 #endif
538 
539 	PopupMultiChoice *texScalingChoice = graphicsSettings->Add(new PopupMultiChoice(&g_Config.iTexScalingLevel, gr->T("Upscale Level"), texScaleLevels, 1, ARRAY_SIZE(texScaleLevels), gr->GetName(), screenManager()));
540 	// TODO: Better check?  When it won't work, it scales down anyway.
541 	if (!gl_extensions.OES_texture_npot && GetGPUBackend() == GPUBackend::OPENGL) {
542 		texScalingChoice->HideChoice(3); // 3x
543 		texScalingChoice->HideChoice(5); // 5x
544 	}
545 	texScalingChoice->OnChoice.Add([=](EventParams &e) {
546 		if (g_Config.iTexScalingLevel != 1 && !UsingHardwareTextureScaling()) {
547 			settingInfo_->Show(gr->T("UpscaleLevel Tip", "CPU heavy - some scaling may be delayed to avoid stutter"), e.v);
548 		}
549 		return UI::EVENT_CONTINUE;
550 	});
551 	texScalingChoice->SetDisabledPtr(&g_Config.bSoftwareRendering);
552 
553 	static const char *texScaleAlgos[] = { "xBRZ", "Hybrid", "Bicubic", "Hybrid + Bicubic", };
554 	PopupMultiChoice *texScalingType = graphicsSettings->Add(new PopupMultiChoice(&g_Config.iTexScalingType, gr->T("Upscale Type"), texScaleAlgos, 0, ARRAY_SIZE(texScaleAlgos), gr->GetName(), screenManager()));
555 	texScalingType->SetEnabledFunc([]() {
556 		return !g_Config.bSoftwareRendering && !UsingHardwareTextureScaling();
557 	});
558 
559 	CheckBox *deposterize = graphicsSettings->Add(new CheckBox(&g_Config.bTexDeposterize, gr->T("Deposterize")));
560 	deposterize->OnClick.Add([=](EventParams &e) {
561 		if (g_Config.bTexDeposterize == true) {
562 			settingInfo_->Show(gr->T("Deposterize Tip", "Fixes visual banding glitches in upscaled textures"), e.v);
563 		}
564 		return UI::EVENT_CONTINUE;
565 	});
566 	deposterize->SetEnabledFunc([]() {
567 		return !g_Config.bSoftwareRendering && !UsingHardwareTextureScaling();
568 	});
569 
570 	ChoiceWithValueDisplay *textureShaderChoice = graphicsSettings->Add(new ChoiceWithValueDisplay(&g_Config.sTextureShaderName, gr->T("Texture Shader"), &TextureTranslateName));
571 	textureShaderChoice->OnClick.Handle(this, &GameSettingsScreen::OnTextureShader);
572 	textureShaderChoice->SetEnabledFunc([]() {
573 		return GetGPUBackend() == GPUBackend::VULKAN && !g_Config.bSoftwareRendering;
574 	});
575 
576 	graphicsSettings->Add(new ItemHeader(gr->T("Texture Filtering")));
577 	static const char *anisoLevels[] = { "Off", "2x", "4x", "8x", "16x" };
578 	PopupMultiChoice *anisoFiltering = graphicsSettings->Add(new PopupMultiChoice(&g_Config.iAnisotropyLevel, gr->T("Anisotropic Filtering"), anisoLevels, 0, ARRAY_SIZE(anisoLevels), gr->GetName(), screenManager()));
579 	anisoFiltering->SetDisabledPtr(&g_Config.bSoftwareRendering);
580 
581 	static const char *texFilters[] = { "Auto", "Nearest", "Linear", "Auto Max Quality"};
582 	graphicsSettings->Add(new PopupMultiChoice(&g_Config.iTexFiltering, gr->T("Texture Filter"), texFilters, 1, ARRAY_SIZE(texFilters), gr->GetName(), screenManager()));
583 
584 	static const char *bufFilters[] = { "Linear", "Nearest", };
585 	graphicsSettings->Add(new PopupMultiChoice(&g_Config.iBufFilter, gr->T("Screen Scaling Filter"), bufFilters, 1, ARRAY_SIZE(bufFilters), gr->GetName(), screenManager()));
586 
587 #if PPSSPP_PLATFORM(ANDROID) || PPSSPP_PLATFORM(IOS)
588 	bool showCardboardSettings = true;
589 #else
590 	// If you enabled it through the ini, you can see this. Useful for testing.
591 	bool showCardboardSettings = g_Config.bEnableCardboardVR;
592 #endif
593 	if (showCardboardSettings) {
594 		graphicsSettings->Add(new ItemHeader(gr->T("Cardboard VR Settings", "Cardboard VR Settings")));
595 		graphicsSettings->Add(new CheckBox(&g_Config.bEnableCardboardVR, gr->T("Enable Cardboard VR", "Enable Cardboard VR")));
596 		PopupSliderChoice *cardboardScreenSize = graphicsSettings->Add(new PopupSliderChoice(&g_Config.iCardboardScreenSize, 30, 150, gr->T("Cardboard Screen Size", "Screen Size (in % of the viewport)"), 1, screenManager(), gr->T("% of viewport")));
597 		cardboardScreenSize->SetEnabledPtr(&g_Config.bEnableCardboardVR);
598 		PopupSliderChoice *cardboardXShift = graphicsSettings->Add(new PopupSliderChoice(&g_Config.iCardboardXShift, -150, 150, gr->T("Cardboard Screen X Shift", "X Shift (in % of the void)"), 1, screenManager(), gr->T("% of the void")));
599 		cardboardXShift->SetEnabledPtr(&g_Config.bEnableCardboardVR);
600 		PopupSliderChoice *cardboardYShift = graphicsSettings->Add(new PopupSliderChoice(&g_Config.iCardboardYShift, -100, 100, gr->T("Cardboard Screen Y Shift", "Y Shift (in % of the void)"), 1, screenManager(), gr->T("% of the void")));
601 		cardboardYShift->SetEnabledPtr(&g_Config.bEnableCardboardVR);
602 	}
603 
604 	std::vector<std::string> cameraList = Camera::getDeviceList();
605 	if (cameraList.size() >= 1) {
606 		graphicsSettings->Add(new ItemHeader(gr->T("Camera")));
607 		PopupMultiChoiceDynamic *cameraChoice = graphicsSettings->Add(new PopupMultiChoiceDynamic(&g_Config.sCameraDevice, gr->T("Camera Device"), cameraList, nullptr, screenManager()));
608 		cameraChoice->OnChoice.Handle(this, &GameSettingsScreen::OnCameraDeviceChange);
609 	}
610 
611 	graphicsSettings->Add(new ItemHeader(gr->T("Hack Settings", "Hack Settings (these WILL cause glitches)")));
612 
613 	static const char *bloomHackOptions[] = { "Off", "Safe", "Balanced", "Aggressive" };
614 	PopupMultiChoice *bloomHack = graphicsSettings->Add(new PopupMultiChoice(&g_Config.iBloomHack, gr->T("Lower resolution for effects (reduces artifacts)"), bloomHackOptions, 0, ARRAY_SIZE(bloomHackOptions), gr->GetName(), screenManager()));
615 	bloomHack->SetEnabledFunc([] {
616 		return !g_Config.bSoftwareRendering && g_Config.iInternalResolution != 1;
617 	});
618 
619 	graphicsSettings->Add(new ItemHeader(gr->T("Overlay Information")));
620 	static const char *fpsChoices[] = { "None", "Speed", "FPS", "Both" };
621 	graphicsSettings->Add(new PopupMultiChoice(&g_Config.iShowFPSCounter, gr->T("Show FPS Counter"), fpsChoices, 0, ARRAY_SIZE(fpsChoices), gr->GetName(), screenManager()));
622 	graphicsSettings->Add(new CheckBox(&g_Config.bShowDebugStats, gr->T("Show Debug Statistics")))->OnClick.Handle(this, &GameSettingsScreen::OnJitAffectingSetting);
623 
624 	// Developer tools are not accessible ingame, so it goes here.
625 	graphicsSettings->Add(new ItemHeader(gr->T("Debugging")));
626 	Choice *dump = graphicsSettings->Add(new Choice(gr->T("Dump next frame to log")));
627 	dump->OnClick.Handle(this, &GameSettingsScreen::OnDumpNextFrameToLog);
628 	if (!PSP_IsInited())
629 		dump->SetEnabled(false);
630 
631 	// Audio
632 	ViewGroup *audioSettingsScroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, FILL_PARENT));
633 	audioSettingsScroll->SetTag("GameSettingsAudio");
634 	LinearLayout *audioSettings = new LinearLayoutList(ORIENT_VERTICAL);
635 	audioSettings->SetSpacing(0);
636 	audioSettingsScroll->Add(audioSettings);
637 	tabHolder->AddTab(ms->T("Audio"), audioSettingsScroll);
638 
639 	audioSettings->Add(new ItemHeader(ms->T("Audio")));
640 
641 	audioSettings->Add(new CheckBox(&g_Config.bEnableSound, a->T("Enable Sound")));
642 
643 	PopupSliderChoice *volume = audioSettings->Add(new PopupSliderChoice(&g_Config.iGlobalVolume, VOLUME_OFF, VOLUME_FULL, a->T("Global volume"), screenManager()));
644 	volume->SetEnabledPtr(&g_Config.bEnableSound);
645 	volume->SetZeroLabel(a->T("Mute"));
646 
647 	PopupSliderChoice *altVolume = audioSettings->Add(new PopupSliderChoice(&g_Config.iAltSpeedVolume, VOLUME_OFF, VOLUME_FULL, a->T("Alternate speed volume"), screenManager()));
648 	altVolume->SetEnabledPtr(&g_Config.bEnableSound);
649 	altVolume->SetZeroLabel(a->T("Mute"));
650 	altVolume->SetNegativeDisable(a->T("Use global volume"));
651 
652 	PopupSliderChoice *reverbVolume = audioSettings->Add(new PopupSliderChoice(&g_Config.iReverbVolume, VOLUME_OFF, 2 * VOLUME_FULL, a->T("Reverb volume"), screenManager()));
653 	reverbVolume->SetEnabledPtr(&g_Config.bEnableSound);
654 	reverbVolume->SetZeroLabel(a->T("Disabled"));
655 
656 	// Hide the backend selector in UWP builds (we only support XAudio2 there).
657 #if PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP)
658 	if (IsVistaOrHigher()) {
659 		static const char *backend[] = { "Auto", "DSound (compatible)", "WASAPI (fast)" };
660 		PopupMultiChoice *audioBackend = audioSettings->Add(new PopupMultiChoice(&g_Config.iAudioBackend, a->T("Audio backend", "Audio backend (restart req.)"), backend, 0, ARRAY_SIZE(backend), a->GetName(), screenManager()));
661 		audioBackend->SetEnabledPtr(&g_Config.bEnableSound);
662 	}
663 #endif
664 
665 	bool sdlAudio = false;
666 #if defined(SDL)
667 	std::vector<std::string> audioDeviceList;
668 	SplitString(System_GetProperty(SYSPROP_AUDIO_DEVICE_LIST), '\0', audioDeviceList);
669 	audioDeviceList.insert(audioDeviceList.begin(), a->T("Auto"));
670 	PopupMultiChoiceDynamic *audioDevice = audioSettings->Add(new PopupMultiChoiceDynamic(&g_Config.sAudioDevice, a->T("Device"), audioDeviceList, nullptr, screenManager()));
671 	audioDevice->OnChoice.Handle(this, &GameSettingsScreen::OnAudioDevice);
672 	sdlAudio = true;
673 #endif
674 
675 	if (sdlAudio || g_Config.iAudioBackend == AUDIO_BACKEND_WASAPI) {
676 		audioSettings->Add(new CheckBox(&g_Config.bAutoAudioDevice, a->T("Use new audio devices automatically")));
677 	}
678 
679 #if PPSSPP_PLATFORM(ANDROID)
680 	CheckBox *extraAudio = audioSettings->Add(new CheckBox(&g_Config.bExtraAudioBuffering, a->T("AudioBufferingForBluetooth", "Bluetooth-friendly buffer (slower)")));
681 	extraAudio->SetEnabledPtr(&g_Config.bEnableSound);
682 
683 	// Show OpenSL debug info
684 	const std::string audioErrorStr = AndroidAudio_GetErrorString(g_audioState);
685 	if (!audioErrorStr.empty()) {
686 		audioSettings->Add(new InfoItem(a->T("Audio Error"), audioErrorStr));
687 	}
688 #endif
689 
690 	std::vector<std::string> micList = Microphone::getDeviceList();
691 	if (!micList.empty()) {
692 		audioSettings->Add(new ItemHeader(a->T("Microphone")));
693 		PopupMultiChoiceDynamic *MicChoice = audioSettings->Add(new PopupMultiChoiceDynamic(&g_Config.sMicDevice, a->T("Microphone Device"), micList, nullptr, screenManager()));
694 		MicChoice->OnChoice.Handle(this, &GameSettingsScreen::OnMicDeviceChange);
695 	}
696 
697 	// Control
698 	ViewGroup *controlsSettingsScroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, FILL_PARENT));
699 	controlsSettingsScroll->SetTag("GameSettingsControls");
700 	LinearLayout *controlsSettings = new LinearLayoutList(ORIENT_VERTICAL);
701 	controlsSettings->SetSpacing(0);
702 	controlsSettingsScroll->Add(controlsSettings);
703 	tabHolder->AddTab(ms->T("Controls"), controlsSettingsScroll);
704 	controlsSettings->Add(new ItemHeader(ms->T("Controls")));
705 	controlsSettings->Add(new Choice(co->T("Control Mapping")))->OnClick.Handle(this, &GameSettingsScreen::OnControlMapping);
706 	controlsSettings->Add(new Choice(co->T("Calibrate Analog Stick")))->OnClick.Handle(this, &GameSettingsScreen::OnCalibrateAnalogs);
707 
708 #if defined(USING_WIN_UI)
709 	controlsSettings->Add(new CheckBox(&g_Config.bSystemControls, co->T("Enable standard shortcut keys")));
710 	controlsSettings->Add(new CheckBox(&g_Config.bGamepadOnlyFocused, co->T("Ignore gamepads when not focused")));
711 #endif
712 
713 	if (System_GetPropertyInt(SYSPROP_DEVICE_TYPE) == DEVICE_TYPE_MOBILE) {
714 		controlsSettings->Add(new CheckBox(&g_Config.bHapticFeedback, co->T("HapticFeedback", "Haptic Feedback (vibration)")));
715 
716 		static const char *tiltTypes[] = { "None (Disabled)", "Analog Stick", "D-PAD", "PSP Action Buttons", "L/R Trigger Buttons" };
717 		controlsSettings->Add(new PopupMultiChoice(&g_Config.iTiltInputType, co->T("Tilt Input Type"), tiltTypes, 0, ARRAY_SIZE(tiltTypes), co->GetName(), screenManager()))->OnClick.Handle(this, &GameSettingsScreen::OnTiltTypeChange);
718 
719 		Choice *customizeTilt = controlsSettings->Add(new Choice(co->T("Customize tilt")));
720 		customizeTilt->OnClick.Handle(this, &GameSettingsScreen::OnTiltCustomize);
721 		customizeTilt->SetEnabledFunc([] {
722 			return g_Config.iTiltInputType != 0;
723 		});
724 	}
725 
726 	// TVs don't have touch control, at least not yet.
727 	if (System_GetPropertyInt(SYSPROP_DEVICE_TYPE) != DEVICE_TYPE_TV) {
728 		controlsSettings->Add(new ItemHeader(co->T("OnScreen", "On-Screen Touch Controls")));
729 		controlsSettings->Add(new CheckBox(&g_Config.bShowTouchControls, co->T("OnScreen", "On-Screen Touch Controls")));
730 		layoutEditorChoice_ = controlsSettings->Add(new Choice(co->T("Customize Touch Controls")));
731 		layoutEditorChoice_->OnClick.Handle(this, &GameSettingsScreen::OnTouchControlLayout);
732 		layoutEditorChoice_->SetEnabledPtr(&g_Config.bShowTouchControls);
733 
734 		// Re-centers itself to the touch location on touch-down.
735 		CheckBox *floatingAnalog = controlsSettings->Add(new CheckBox(&g_Config.bAutoCenterTouchAnalog, co->T("Auto-centering analog stick")));
736 		floatingAnalog->SetEnabledPtr(&g_Config.bShowTouchControls);
737 
738 		// Hide stick background, usefull when increasing the size
739 		CheckBox *hideStickBackground = controlsSettings->Add(new CheckBox(&g_Config.bHideStickBackground, co->T("Hide touch analog stick background circle")));
740 		hideStickBackground->SetEnabledPtr(&g_Config.bShowTouchControls);
741 
742 		// On non iOS systems, offer to let the user see this button.
743 		// Some Windows touch devices don't have a back button or other button to call up the menu.
744 		if (System_GetPropertyBool(SYSPROP_HAS_BACK_BUTTON)) {
745 			CheckBox *enablePauseBtn = controlsSettings->Add(new CheckBox(&g_Config.bShowTouchPause, co->T("Show Touch Pause Menu Button")));
746 
747 			// Don't allow the user to disable it once in-game, so they can't lock themselves out of the menu.
748 			if (!PSP_IsInited()) {
749 				enablePauseBtn->SetEnabledPtr(&g_Config.bShowTouchControls);
750 			} else {
751 				enablePauseBtn->SetEnabled(false);
752 			}
753 		}
754 
755 		CheckBox *disableDiags = controlsSettings->Add(new CheckBox(&g_Config.bDisableDpadDiagonals, co->T("Disable D-Pad diagonals (4-way touch)")));
756 		disableDiags->SetEnabledPtr(&g_Config.bShowTouchControls);
757 		PopupSliderChoice *opacity = controlsSettings->Add(new PopupSliderChoice(&g_Config.iTouchButtonOpacity, 0, 100, co->T("Button Opacity"), screenManager(), "%"));
758 		opacity->SetEnabledPtr(&g_Config.bShowTouchControls);
759 		opacity->SetFormat("%i%%");
760 		PopupSliderChoice *autoHide = controlsSettings->Add(new PopupSliderChoice(&g_Config.iTouchButtonHideSeconds, 0, 300, co->T("Auto-hide buttons after seconds"), screenManager(), co->T("seconds, 0 : off")));
761 		autoHide->SetEnabledPtr(&g_Config.bShowTouchControls);
762 		autoHide->SetFormat("%is");
763 		autoHide->SetZeroLabel(co->T("Off"));
764 		static const char *touchControlStyles[] = {"Classic", "Thin borders", "Glowing borders"};
765 		View *style = controlsSettings->Add(new PopupMultiChoice(&g_Config.iTouchButtonStyle, co->T("Button style"), touchControlStyles, 0, ARRAY_SIZE(touchControlStyles), co->GetName(), screenManager()));
766 		style->SetEnabledPtr(&g_Config.bShowTouchControls);
767 		Choice *gesture = controlsSettings->Add(new Choice(co->T("Gesture mapping")));
768 		gesture->OnClick.Add([=](EventParams &e) {
769 			screenManager()->push(new GestureMappingScreen());
770 			return UI::EVENT_DONE;
771 		});
772 		gesture->SetEnabledPtr(&g_Config.bShowTouchControls);
773 	}
774 
775 	controlsSettings->Add(new ItemHeader(co->T("Keyboard", "Keyboard Control Settings")));
776 #if defined(USING_WIN_UI)
777 	controlsSettings->Add(new CheckBox(&g_Config.bIgnoreWindowsKey, co->T("Ignore Windows Key")));
778 #endif // #if defined(USING_WIN_UI)
779 	auto analogLimiter = new PopupSliderChoiceFloat(&g_Config.fAnalogLimiterDeadzone, 0.0f, 1.0f, co->T("Analog Limiter"), 0.10f, screenManager(), "/ 1.0");
780 	controlsSettings->Add(analogLimiter);
781 	analogLimiter->OnChange.Add([=](EventParams &e) {
782 		settingInfo_->Show(co->T("AnalogLimiter Tip", "When the analog limiter button is pressed"), e.v);
783 		return UI::EVENT_CONTINUE;
784 	});
785 #if defined(USING_WIN_UI) || defined(SDL)
786 	controlsSettings->Add(new ItemHeader(co->T("Mouse", "Mouse settings")));
787 	CheckBox *mouseControl = controlsSettings->Add(new CheckBox(&g_Config.bMouseControl, co->T("Use Mouse Control")));
788 	mouseControl->OnClick.Add([=](EventParams &e) {
789 		if(g_Config.bMouseControl)
790 			settingInfo_->Show(co->T("MouseControl Tip", "You can now map mouse in control mapping screen by pressing the 'M' icon."), e.v);
791 		return UI::EVENT_CONTINUE;
792 	});
793 	controlsSettings->Add(new CheckBox(&g_Config.bMouseConfine, co->T("Confine Mouse", "Trap mouse within window/display area")))->SetEnabledPtr(&g_Config.bMouseControl);
794 	controlsSettings->Add(new PopupSliderChoiceFloat(&g_Config.fMouseSensitivity, 0.01f, 1.0f, co->T("Mouse sensitivity"), 0.01f, screenManager(), "x"))->SetEnabledPtr(&g_Config.bMouseControl);
795 	controlsSettings->Add(new PopupSliderChoiceFloat(&g_Config.fMouseSmoothing, 0.0f, 0.95f, co->T("Mouse smoothing"), 0.05f, screenManager(), "x"))->SetEnabledPtr(&g_Config.bMouseControl);
796 #endif
797 
798 	ViewGroup *networkingSettingsScroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, FILL_PARENT));
799 	networkingSettingsScroll->SetTag("GameSettingsNetworking");
800 	LinearLayout *networkingSettings = new LinearLayoutList(ORIENT_VERTICAL);
801 	networkingSettings->SetSpacing(0);
802 	networkingSettingsScroll->Add(networkingSettings);
803 	tabHolder->AddTab(ms->T("Networking"), networkingSettingsScroll);
804 
805 	networkingSettings->Add(new ItemHeader(ms->T("Networking")));
806 
807 	networkingSettings->Add(new Choice(n->T("PPSSPP Ad-Hoc Wiki Page")))->OnClick.Handle(this, &GameSettingsScreen::OnAdhocGuides);
808 
809 	networkingSettings->Add(new CheckBox(&g_Config.bEnableWlan, n->T("Enable networking", "Enable networking/wlan (beta)")));
810 	networkingSettings->Add(new ChoiceWithValueDisplay(&g_Config.sMACAddress, n->T("Change Mac Address"), (const char*)nullptr))->OnClick.Handle(this, &GameSettingsScreen::OnChangeMacAddress);
811 	static const char* wlanChannels[] = { "Auto", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11" };
812 	auto wlanChannelChoice = networkingSettings->Add(new PopupMultiChoice(&g_Config.iWlanAdhocChannel, n->T("WLAN Channel"), wlanChannels, 0, ARRAY_SIZE(wlanChannels), n->GetName(), screenManager()));
813 	for (int i = 0; i < 4; i++) {
814 		wlanChannelChoice->HideChoice(i + 2);
815 		wlanChannelChoice->HideChoice(i + 7);
816 	}
817 	networkingSettings->Add(new CheckBox(&g_Config.bDiscordPresence, n->T("Send Discord Presence information")));
818 
819 	networkingSettings->Add(new ItemHeader(n->T("AdHoc Server")));
820 	networkingSettings->Add(new CheckBox(&g_Config.bEnableAdhocServer, n->T("Enable built-in PRO Adhoc Server", "Enable built-in PRO Adhoc Server")));
821 	networkingSettings->Add(new ChoiceWithValueDisplay(&g_Config.proAdhocServer, n->T("Change proAdhocServer Address", "Change proAdhocServer Address (localhost = multiple instance)"), (const char*)nullptr))->OnClick.Handle(this, &GameSettingsScreen::OnChangeproAdhocServerAddress);
822 
823 	networkingSettings->Add(new ItemHeader(n->T("UPnP (port-forwarding)")));
824 	networkingSettings->Add(new CheckBox(&g_Config.bEnableUPnP, n->T("Enable UPnP", "Enable UPnP (need a few seconds to detect)")));
825 	networkingSettings->Add(new CheckBox(&g_Config.bUPnPUseOriginalPort, n->T("UPnP use original port", "UPnP use original port (Enabled = PSP compatibility)")))->SetEnabledPtr(&g_Config.bEnableUPnP);
826 
827 	networkingSettings->Add(new ItemHeader(n->T("Chat")));
828 	networkingSettings->Add(new CheckBox(&g_Config.bEnableNetworkChat, n->T("Enable network chat", "Enable network chat")));
829 	static const char *chatButtonPositions[] = { "Bottom Left", "Bottom Center", "Bottom Right", "Top Left", "Top Center", "Top Right", "Center Left", "Center Right", "None" };
830 	networkingSettings->Add(new PopupMultiChoice(&g_Config.iChatButtonPosition, n->T("Chat Button Position"), chatButtonPositions, 0, ARRAY_SIZE(chatButtonPositions), n->GetName(), screenManager()))->SetEnabledPtr(&g_Config.bEnableNetworkChat);
831 	static const char *chatScreenPositions[] = { "Bottom Left", "Bottom Center", "Bottom Right", "Top Left", "Top Center", "Top Right" };
832 	networkingSettings->Add(new PopupMultiChoice(&g_Config.iChatScreenPosition, n->T("Chat Screen Position"), chatScreenPositions, 0, ARRAY_SIZE(chatScreenPositions), n->GetName(), screenManager()))->SetEnabledPtr(&g_Config.bEnableNetworkChat);
833 
834 #if (!defined(MOBILE_DEVICE) && !defined(USING_QT_UI)) || defined(USING_QT_UI) || PPSSPP_PLATFORM(ANDROID) // Missing only iOS?
835 	networkingSettings->Add(new ItemHeader(n->T("QuickChat", "Quick Chat")));
836 	CheckBox *qc = networkingSettings->Add(new CheckBox(&g_Config.bEnableQuickChat, n->T("EnableQuickChat", "Enable Quick Chat")));
837 	qc->SetEnabledPtr(&g_Config.bEnableNetworkChat);
838 #endif
839 
840 #if !defined(MOBILE_DEVICE) && !defined(USING_QT_UI)  // TODO: Add all platforms where KEY_CHAR support is added
841 	PopupTextInputChoice *qc1 = networkingSettings->Add(new PopupTextInputChoice(&g_Config.sQuickChat0, n->T("Quick Chat 1"), "", 32, screenManager()));
842 	PopupTextInputChoice *qc2 = networkingSettings->Add(new PopupTextInputChoice(&g_Config.sQuickChat1, n->T("Quick Chat 2"), "", 32, screenManager()));
843 	PopupTextInputChoice *qc3 = networkingSettings->Add(new PopupTextInputChoice(&g_Config.sQuickChat2, n->T("Quick Chat 3"), "", 32, screenManager()));
844 	PopupTextInputChoice *qc4 = networkingSettings->Add(new PopupTextInputChoice(&g_Config.sQuickChat3, n->T("Quick Chat 4"), "", 32, screenManager()));
845 	PopupTextInputChoice *qc5 = networkingSettings->Add(new PopupTextInputChoice(&g_Config.sQuickChat4, n->T("Quick Chat 5"), "", 32, screenManager()));
846 #elif defined(USING_QT_UI)
847 	Choice *qc1 = networkingSettings->Add(new Choice(n->T("Quick Chat 1")));
848 	Choice *qc2 = networkingSettings->Add(new Choice(n->T("Quick Chat 2")));
849 	Choice *qc3 = networkingSettings->Add(new Choice(n->T("Quick Chat 3")));
850 	Choice *qc4 = networkingSettings->Add(new Choice(n->T("Quick Chat 4")));
851 	Choice *qc5 = networkingSettings->Add(new Choice(n->T("Quick Chat 5")));
852 #elif PPSSPP_PLATFORM(ANDROID)
853 	ChoiceWithValueDisplay *qc1 = networkingSettings->Add(new ChoiceWithValueDisplay(&g_Config.sQuickChat0, n->T("Quick Chat 1"), (const char *)nullptr));
854 	ChoiceWithValueDisplay *qc2 = networkingSettings->Add(new ChoiceWithValueDisplay(&g_Config.sQuickChat1, n->T("Quick Chat 2"), (const char *)nullptr));
855 	ChoiceWithValueDisplay *qc3 = networkingSettings->Add(new ChoiceWithValueDisplay(&g_Config.sQuickChat2, n->T("Quick Chat 3"), (const char *)nullptr));
856 	ChoiceWithValueDisplay *qc4 = networkingSettings->Add(new ChoiceWithValueDisplay(&g_Config.sQuickChat3, n->T("Quick Chat 4"), (const char *)nullptr));
857 	ChoiceWithValueDisplay *qc5 = networkingSettings->Add(new ChoiceWithValueDisplay(&g_Config.sQuickChat4, n->T("Quick Chat 5"), (const char *)nullptr));
858 #endif
859 
860 #if (!defined(MOBILE_DEVICE) && !defined(USING_QT_UI)) || defined(USING_QT_UI) || PPSSPP_PLATFORM(ANDROID)
861 	qc1->SetEnabledFunc([] { return g_Config.bEnableQuickChat && g_Config.bEnableNetworkChat; });
862 	qc2->SetEnabledFunc([] { return g_Config.bEnableQuickChat && g_Config.bEnableNetworkChat; });
863 	qc3->SetEnabledFunc([] { return g_Config.bEnableQuickChat && g_Config.bEnableNetworkChat; });
864 	qc4->SetEnabledFunc([] { return g_Config.bEnableQuickChat && g_Config.bEnableNetworkChat; });
865 	qc5->SetEnabledFunc([] { return g_Config.bEnableQuickChat && g_Config.bEnableNetworkChat; });
866 #endif
867 
868 #if defined(USING_QT_UI) || PPSSPP_PLATFORM(ANDROID)
869 	qc1->OnClick.Handle(this, &GameSettingsScreen::OnChangeQuickChat0);
870 	qc2->OnClick.Handle(this, &GameSettingsScreen::OnChangeQuickChat1);
871 	qc3->OnClick.Handle(this, &GameSettingsScreen::OnChangeQuickChat2);
872 	qc4->OnClick.Handle(this, &GameSettingsScreen::OnChangeQuickChat3);
873 	qc5->OnClick.Handle(this, &GameSettingsScreen::OnChangeQuickChat4);
874 #endif
875 
876 	networkingSettings->Add(new ItemHeader(n->T("Misc", "Misc (default = compatibility)")));
877 	networkingSettings->Add(new PopupSliderChoice(&g_Config.iPortOffset, 0, 60000, n->T("Port offset", "Port offset (0 = PSP compatibility)"), 100, screenManager()));
878 	networkingSettings->Add(new PopupSliderChoice(&g_Config.iMinTimeout, 0, 15000, n->T("Minimum Timeout", "Minimum Timeout (override in ms, 0 = default)"), 50, screenManager()));
879 	networkingSettings->Add(new CheckBox(&g_Config.bForcedFirstConnect, n->T("Forced First Connect", "Forced First Connect (faster Connect)")));
880 
881 	ViewGroup *toolsScroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, FILL_PARENT));
882 	toolsScroll->SetTag("GameSettingsTools");
883 	LinearLayout *tools = new LinearLayoutList(ORIENT_VERTICAL);
884 	tools->SetSpacing(0);
885 	toolsScroll->Add(tools);
886 	tabHolder->AddTab(ms->T("Tools"), toolsScroll);
887 
888 	tools->Add(new ItemHeader(ms->T("Tools")));
889 	// These were moved here so use the wrong translation objects, to avoid having to change all inis... This isn't a sustainable situation :P
890 	tools->Add(new Choice(sa->T("Savedata Manager")))->OnClick.Handle(this, &GameSettingsScreen::OnSavedataManager);
891 	tools->Add(new Choice(dev->T("System Information")))->OnClick.Handle(this, &GameSettingsScreen::OnSysInfo);
892 	tools->Add(new Choice(sy->T("Developer Tools")))->OnClick.Handle(this, &GameSettingsScreen::OnDeveloperTools);
893 	tools->Add(new Choice(ri->T("Remote disc streaming")))->OnClick.Handle(this, &GameSettingsScreen::OnRemoteISO);
894 
895 	// System
896 	ViewGroup *systemSettingsScroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, FILL_PARENT));
897 	systemSettingsScroll->SetTag("GameSettingsSystem");
898 	LinearLayout *systemSettings = new LinearLayoutList(ORIENT_VERTICAL);
899 	systemSettings->SetSpacing(0);
900 	systemSettingsScroll->Add(systemSettings);
901 	tabHolder->AddTab(ms->T("System"), systemSettingsScroll);
902 
903 	systemSettings->Add(new ItemHeader(sy->T("UI")));
904 	systemSettings->Add(new Choice(dev->T("Language", "Language")))->OnClick.Handle(this, &GameSettingsScreen::OnLanguage);
905 	systemSettings->Add(new CheckBox(&g_Config.bUISound, sy->T("UI Sound")));
906 	const Path bgPng = GetSysDirectory(DIRECTORY_SYSTEM) / "background.png";
907 	const Path bgJpg = GetSysDirectory(DIRECTORY_SYSTEM) / "background.jpg";
908 	if (File::Exists(bgPng) || File::Exists(bgJpg)) {
909 		backgroundChoice_ = systemSettings->Add(new Choice(sy->T("Clear UI background")));
910 	} else if (System_GetPropertyBool(SYSPROP_HAS_IMAGE_BROWSER)) {
911 		backgroundChoice_ = systemSettings->Add(new Choice(sy->T("Set UI background...")));
912 	} else {
913 		backgroundChoice_ = nullptr;
914 	}
915 	if (backgroundChoice_ != nullptr) {
916 		backgroundChoice_->OnClick.Handle(this, &GameSettingsScreen::OnChangeBackground);
917 	}
918 	static const char *backgroundAnimations[] = { "No animation", "Floating symbols", "Recent games", "Waves", "Moving background" };
919 	systemSettings->Add(new PopupMultiChoice(&g_Config.iBackgroundAnimation, sy->T("UI background animation"), backgroundAnimations, 0, ARRAY_SIZE(backgroundAnimations), sy->GetName(), screenManager()));
920 
921 	systemSettings->Add(new ItemHeader(sy->T("PSP Memory Stick")));
922 
923 #if PPSSPP_PLATFORM(ANDROID)
924 	memstickDisplay_ = g_Config.memStickDirectory.ToVisualString();
925 	auto memstickPath = systemSettings->Add(new ChoiceWithValueDisplay(&memstickDisplay_, sy->T("Memory Stick folder", "Memory Stick folder"), (const char *)nullptr));
926 	memstickPath->SetEnabled(!PSP_IsInited());
927 	memstickPath->OnClick.Handle(this, &GameSettingsScreen::OnChangeMemStickDir);
928 
929 	// Display USB path for convenience.
930 	std::string usbPath;
931 	if (PathToVisualUsbPath(g_Config.memStickDirectory, usbPath)) {
932 		if (usbPath.empty()) {
933 			// Probably it's just the root. So let's add PSP to make it clear.
934 			usbPath = "/PSP";
935 		}
936 		systemSettings->Add(new InfoItem(sy->T("USB"), usbPath))->SetChoiceStyle(true);
937 	}
938 #elif defined(_WIN32) && !PPSSPP_PLATFORM(UWP)
939 	SavePathInMyDocumentChoice = systemSettings->Add(new CheckBox(&installed_, sy->T("Save path in My Documents", "Save path in My Documents")));
940 	SavePathInMyDocumentChoice->SetEnabled(!PSP_IsInited());
941 	SavePathInMyDocumentChoice->OnClick.Handle(this, &GameSettingsScreen::OnSavePathMydoc);
942 	SavePathInOtherChoice = systemSettings->Add(new CheckBox(&otherinstalled_, sy->T("Save path in installed.txt", "Save path in installed.txt")));
943 	SavePathInOtherChoice->SetEnabled(false);
944 	SavePathInOtherChoice->OnClick.Handle(this, &GameSettingsScreen::OnSavePathOther);
945 	const bool myDocsExists = W32Util::UserDocumentsPath().size() != 0;
946 
947 	const Path &PPSSPPpath = File::GetExeDirectory();
948 	const Path installedFile = PPSSPPpath / "installed.txt";
949 	installed_ = File::Exists(installedFile);
950 	otherinstalled_ = false;
951 	if (!installed_ && myDocsExists) {
952 		if (File::CreateEmptyFile(PPSSPPpath / "installedTEMP.txt")) {
953 			// Disable the setting whether cannot create & delete file
954 			if (!(File::Delete(PPSSPPpath / "installedTEMP.txt")))
955 				SavePathInMyDocumentChoice->SetEnabled(false);
956 			else
957 				SavePathInOtherChoice->SetEnabled(!PSP_IsInited());
958 		} else
959 			SavePathInMyDocumentChoice->SetEnabled(false);
960 	} else {
961 		if (installed_ && myDocsExists) {
962 			FILE *testInstalled = File::OpenCFile(installedFile, "rt");
963 			if (testInstalled) {
964 				char temp[2048];
965 				char *tempStr = fgets(temp, sizeof(temp), testInstalled);
966 				// Skip UTF-8 encoding bytes if there are any. There are 3 of them.
967 				if (tempStr && strncmp(tempStr, "\xEF\xBB\xBF", 3) == 0) {
968 					tempStr += 3;
969 				}
970 				SavePathInOtherChoice->SetEnabled(!PSP_IsInited());
971 				if (tempStr && strlen(tempStr) != 0 && strcmp(tempStr, "\n") != 0) {
972 					installed_ = false;
973 					otherinstalled_ = true;
974 				}
975 				fclose(testInstalled);
976 			}
977 		} else if (!myDocsExists) {
978 			SavePathInMyDocumentChoice->SetEnabled(false);
979 		}
980 	}
981 #endif
982 	systemSettings->Add(new CheckBox(&g_Config.bMemStickInserted, sy->T("Memory Stick inserted")));
983 	UI::PopupSliderChoice *sizeChoice = systemSettings->Add(new PopupSliderChoice(&g_Config.iMemStickSizeGB, 1, 32, sy->T("Memory Stick size", "Memory Stick size"), screenManager(), "GB"));
984 	sizeChoice->SetFormat("%d GB");
985 
986 	systemSettings->Add(new ItemHeader(sy->T("Help the PPSSPP team")));
987 	if (!enableReportsSet_)
988 		enableReports_ = Reporting::IsEnabled();
989 	enableReportsSet_ = true;
990 	enableReportsCheckbox_ = new CheckBox(&enableReports_, sy->T("Enable Compatibility Server Reports"));
991 	enableReportsCheckbox_->SetEnabled(Reporting::IsSupported());
992 	systemSettings->Add(enableReportsCheckbox_);
993 
994 	systemSettings->Add(new ItemHeader(sy->T("Emulation")));
995 
996 	systemSettings->Add(new CheckBox(&g_Config.bFastMemory, sy->T("Fast Memory", "Fast Memory")))->OnClick.Handle(this, &GameSettingsScreen::OnJitAffectingSetting);
997 	systemSettings->Add(new CheckBox(&g_Config.bIgnoreBadMemAccess, sy->T("Ignore bad memory accesses")));
998 
999 	systemSettings->Add(new CheckBox(&g_Config.bSeparateIOThread, sy->T("I/O on thread (experimental)")))->SetEnabled(!PSP_IsInited());
1000 	static const char *ioTimingMethods[] = { "Fast (lag on slow storage)", "Host (bugs, less lag)", "Simulate UMD delays" };
1001 	View *ioTimingMethod = systemSettings->Add(new PopupMultiChoice(&g_Config.iIOTimingMethod, sy->T("IO timing method"), ioTimingMethods, 0, ARRAY_SIZE(ioTimingMethods), sy->GetName(), screenManager()));
1002 	ioTimingMethod->SetEnabledPtr(&g_Config.bSeparateIOThread);
1003 	systemSettings->Add(new CheckBox(&g_Config.bForceLagSync, sy->T("Force real clock sync (slower, less lag)")));
1004 	PopupSliderChoice *lockedMhz = systemSettings->Add(new PopupSliderChoice(&g_Config.iLockedCPUSpeed, 0, 1000, sy->T("Change CPU Clock", "Change CPU Clock (unstable)"), screenManager(), sy->T("MHz, 0:default")));
1005 	lockedMhz->OnChange.Add([&](UI::EventParams &) {
1006 		enableReportsCheckbox_->SetEnabled(Reporting::IsSupported());
1007 		return UI::EVENT_CONTINUE;
1008 	});
1009 	lockedMhz->SetZeroLabel(sy->T("Auto"));
1010 	PopupSliderChoice *rewindFreq = systemSettings->Add(new PopupSliderChoice(&g_Config.iRewindFlipFrequency, 0, 1800, sy->T("Rewind Snapshot Frequency", "Rewind Snapshot Frequency (mem hog)"), screenManager(), sy->T("frames, 0:off")));
1011 	rewindFreq->SetZeroLabel(sy->T("Off"));
1012 
1013 	systemSettings->Add(new ItemHeader(sy->T("General")));
1014 
1015 #if PPSSPP_PLATFORM(ANDROID)
1016 	if (System_GetPropertyInt(SYSPROP_DEVICE_TYPE) == DEVICE_TYPE_MOBILE) {
1017 		static const char *screenRotation[] = { "Auto", "Landscape", "Portrait", "Landscape Reversed", "Portrait Reversed", "Landscape Auto" };
1018 		PopupMultiChoice *rot = systemSettings->Add(new PopupMultiChoice(&g_Config.iScreenRotation, co->T("Screen Rotation"), screenRotation, 0, ARRAY_SIZE(screenRotation), co->GetName(), screenManager()));
1019 		rot->OnChoice.Handle(this, &GameSettingsScreen::OnScreenRotation);
1020 
1021 		if (System_GetPropertyBool(SYSPROP_SUPPORTS_SUSTAINED_PERF_MODE)) {
1022 			systemSettings->Add(new CheckBox(&g_Config.bSustainedPerformanceMode, sy->T("Sustained performance mode")))->OnClick.Handle(this, &GameSettingsScreen::OnSustainedPerformanceModeChange);
1023 		}
1024 	}
1025 #endif
1026 	systemSettings->Add(new CheckBox(&g_Config.bCheckForNewVersion, sy->T("VersionCheck", "Check for new versions of PPSSPP")));
1027 
1028 	systemSettings->Add(new Choice(sy->T("Restore Default Settings")))->OnClick.Handle(this, &GameSettingsScreen::OnRestoreDefaultSettings);
1029 	systemSettings->Add(new CheckBox(&g_Config.bEnableStateUndo, sy->T("Savestate slot backups")));
1030 	static const char *autoLoadSaveStateChoices[] = { "Off", "Oldest Save", "Newest Save", "Slot 1", "Slot 2", "Slot 3", "Slot 4", "Slot 5" };
1031 	systemSettings->Add(new PopupMultiChoice(&g_Config.iAutoLoadSaveState, sy->T("Auto Load Savestate"), autoLoadSaveStateChoices, 0, ARRAY_SIZE(autoLoadSaveStateChoices), sy->GetName(), screenManager()));
1032 #if defined(USING_WIN_UI) || defined(USING_QT_UI) || PPSSPP_PLATFORM(ANDROID)
1033 	systemSettings->Add(new CheckBox(&g_Config.bBypassOSKWithKeyboard, sy->T("Use system native keyboard")));
1034 #endif
1035 
1036 #if PPSSPP_ARCH(AMD64)
1037 	systemSettings->Add(new CheckBox(&g_Config.bCacheFullIsoInRam, sy->T("Cache ISO in RAM", "Cache full ISO in RAM")))->SetEnabled(!PSP_IsInited());
1038 #endif
1039 
1040 	systemSettings->Add(new ItemHeader(sy->T("Cheats", "Cheats")));
1041 	CheckBox *enableCheats = systemSettings->Add(new CheckBox(&g_Config.bEnableCheats, sy->T("Enable Cheats")));
1042 	enableCheats->OnClick.Add([&](UI::EventParams &) {
1043 		enableReportsCheckbox_->SetEnabled(Reporting::IsSupported());
1044 		return UI::EVENT_CONTINUE;
1045 	});
1046 	systemSettings->SetSpacing(0);
1047 
1048 	systemSettings->Add(new ItemHeader(sy->T("PSP Settings")));
1049 	static const char *models[] = {"PSP-1000", "PSP-2000/3000"};
1050 	systemSettings->Add(new PopupMultiChoice(&g_Config.iPSPModel, sy->T("PSP Model"), models, 0, ARRAY_SIZE(models), sy->GetName(), screenManager()))->SetEnabled(!PSP_IsInited());
1051 	// TODO: Come up with a way to display a keyboard for mobile users,
1052 	// so until then, this is Windows/Desktop only.
1053 #if !defined(MOBILE_DEVICE)  // TODO: Add all platforms where KEY_CHAR support is added
1054 	systemSettings->Add(new PopupTextInputChoice(&g_Config.sNickName, sy->T("Change Nickname"), "", 32, screenManager()));
1055 #elif PPSSPP_PLATFORM(ANDROID)
1056 	systemSettings->Add(new ChoiceWithValueDisplay(&g_Config.sNickName, sy->T("Change Nickname"), (const char *)nullptr))->OnClick.Handle(this, &GameSettingsScreen::OnChangeNickname);
1057 #endif
1058 
1059 	systemSettings->Add(new CheckBox(&g_Config.bScreenshotsAsPNG, sy->T("Screenshots as PNG")));
1060 
1061 #if defined(_WIN32) || (defined(USING_QT_UI) && !defined(MOBILE_DEVICE))
1062 	systemSettings->Add(new CheckBox(&g_Config.bDumpFrames, sy->T("Record Display")));
1063 	systemSettings->Add(new CheckBox(&g_Config.bUseFFV1, sy->T("Use Lossless Video Codec (FFV1)")));
1064 	systemSettings->Add(new CheckBox(&g_Config.bDumpVideoOutput, sy->T("Use output buffer (with overlay) for recording")));
1065 	systemSettings->Add(new CheckBox(&g_Config.bDumpAudio, sy->T("Record Audio")));
1066 	systemSettings->Add(new CheckBox(&g_Config.bSaveLoadResetsAVdumping, sy->T("Reset Recording on Save/Load State")));
1067 #endif
1068 	systemSettings->Add(new CheckBox(&g_Config.bDayLightSavings, sy->T("Day Light Saving")));
1069 	static const char *dateFormat[] = { "YYYYMMDD", "MMDDYYYY", "DDMMYYYY"};
1070 	systemSettings->Add(new PopupMultiChoice(&g_Config.iDateFormat, sy->T("Date Format"), dateFormat, 0, 3, sy->GetName(), screenManager()));
1071 	static const char *timeFormat[] = { "24HR", "12HR" };
1072 	systemSettings->Add(new PopupMultiChoice(&g_Config.iTimeFormat, sy->T("Time Format"), timeFormat, 0, 2, sy->GetName(), screenManager()));
1073 	static const char *buttonPref[] = { "Use O to confirm", "Use X to confirm" };
1074 	systemSettings->Add(new PopupMultiChoice(&g_Config.iButtonPreference, sy->T("Confirmation Button"), buttonPref, 0, 2, sy->GetName(), screenManager()));
1075 }
1076 
OnAutoFrameskip(UI::EventParams & e)1077 UI::EventReturn GameSettingsScreen::OnAutoFrameskip(UI::EventParams &e) {
1078 	if (g_Config.bAutoFrameSkip && g_Config.iFrameSkip == 0) {
1079 		g_Config.iFrameSkip = 1;
1080 	}
1081 	if (g_Config.bAutoFrameSkip && g_Config.iRenderingMode == FB_NON_BUFFERED_MODE) {
1082 		g_Config.iRenderingMode = FB_BUFFERED_MODE;
1083 	}
1084 	return UI::EVENT_DONE;
1085 }
1086 
OnSoftwareRendering(UI::EventParams & e)1087 UI::EventReturn GameSettingsScreen::OnSoftwareRendering(UI::EventParams &e) {
1088 	tessHWEnable_ = DoesBackendSupportHWTess() && !g_Config.bSoftwareRendering && g_Config.bHardwareTransform;
1089 	return UI::EVENT_DONE;
1090 }
1091 
OnHardwareTransform(UI::EventParams & e)1092 UI::EventReturn GameSettingsScreen::OnHardwareTransform(UI::EventParams &e) {
1093 	tessHWEnable_ = DoesBackendSupportHWTess() && !g_Config.bSoftwareRendering && g_Config.bHardwareTransform;
1094 	return UI::EVENT_DONE;
1095 }
1096 
OnScreenRotation(UI::EventParams & e)1097 UI::EventReturn GameSettingsScreen::OnScreenRotation(UI::EventParams &e) {
1098 	INFO_LOG(SYSTEM, "New display rotation: %d", g_Config.iScreenRotation);
1099 	INFO_LOG(SYSTEM, "Sending rotate");
1100 	System_SendMessage("rotate", "");
1101 	INFO_LOG(SYSTEM, "Got back from rotate");
1102 	return UI::EVENT_DONE;
1103 }
1104 
RecreateActivity()1105 void RecreateActivity() {
1106 	const int SYSTEM_JELLYBEAN = 16;
1107 	if (System_GetPropertyInt(SYSPROP_SYSTEMVERSION) >= SYSTEM_JELLYBEAN) {
1108 		INFO_LOG(SYSTEM, "Sending recreate");
1109 		System_SendMessage("recreate", "");
1110 		INFO_LOG(SYSTEM, "Got back from recreate");
1111 	} else {
1112 		auto gr = GetI18NCategory("Graphics");
1113 		System_SendMessage("toast", gr->T("Must Restart", "You must restart PPSSPP for this change to take effect"));
1114 	}
1115 }
1116 
OnAdhocGuides(UI::EventParams & e)1117 UI::EventReturn GameSettingsScreen::OnAdhocGuides(UI::EventParams &e) {
1118 	auto n = GetI18NCategory("Networking");
1119 	LaunchBrowser(n->T("MultiplayerHowToURL", "https://github.com/hrydgard/ppsspp/wiki/How-to-play-multiplayer-games-with-PPSSPP"));
1120 	return UI::EVENT_DONE;
1121 }
1122 
OnImmersiveModeChange(UI::EventParams & e)1123 UI::EventReturn GameSettingsScreen::OnImmersiveModeChange(UI::EventParams &e) {
1124 	System_SendMessage("immersive", "");
1125 	if (g_Config.iAndroidHwScale != 0) {
1126 		RecreateActivity();
1127 	}
1128 	return UI::EVENT_DONE;
1129 }
1130 
OnSustainedPerformanceModeChange(UI::EventParams & e)1131 UI::EventReturn GameSettingsScreen::OnSustainedPerformanceModeChange(UI::EventParams &e) {
1132 	System_SendMessage("sustainedPerfMode", "");
1133 	return UI::EVENT_DONE;
1134 }
1135 
OnRenderingMode(UI::EventParams & e)1136 UI::EventReturn GameSettingsScreen::OnRenderingMode(UI::EventParams &e) {
1137 	// We do not want to report when rendering mode is Framebuffer to memory - so many issues
1138 	// are caused by that (framebuffer copies overwriting display lists, etc).
1139 	Reporting::UpdateConfig();
1140 	enableReportsCheckbox_->SetEnabled(Reporting::IsSupported());
1141 	if (!Reporting::IsSupported())
1142 		enableReports_ = Reporting::IsEnabled();
1143 
1144 	if (g_Config.iRenderingMode == FB_NON_BUFFERED_MODE) {
1145 		g_Config.bAutoFrameSkip = false;
1146 	}
1147 	return UI::EVENT_DONE;
1148 }
1149 
OnJitAffectingSetting(UI::EventParams & e)1150 UI::EventReturn GameSettingsScreen::OnJitAffectingSetting(UI::EventParams &e) {
1151 	NativeMessageReceived("clear jit", "");
1152 	return UI::EVENT_DONE;
1153 }
1154 
OnChangeMemStickDir(UI::EventParams & e)1155 UI::EventReturn GameSettingsScreen::OnChangeMemStickDir(UI::EventParams &e) {
1156 	screenManager()->push(new MemStickScreen(false));
1157 	return UI::EVENT_DONE;
1158 }
1159 
1160 #if defined(_WIN32) && !PPSSPP_PLATFORM(UWP)
1161 
OnSavePathMydoc(UI::EventParams & e)1162 UI::EventReturn GameSettingsScreen::OnSavePathMydoc(UI::EventParams &e) {
1163 	const Path &PPSSPPpath = File::GetExeDirectory();
1164 	const Path installedFile = PPSSPPpath / "installed.txt";
1165 	installed_ = File::Exists(installedFile);
1166 	if (otherinstalled_) {
1167 		File::Delete(PPSSPPpath / "installed.txt");
1168 		File::CreateEmptyFile(PPSSPPpath / "installed.txt");
1169 		otherinstalled_ = false;
1170 		const std::string myDocsPath = W32Util::UserDocumentsPath() + "/PPSSPP/";
1171 		g_Config.memStickDirectory = Path(myDocsPath);
1172 	} else if (installed_) {
1173 		File::Delete(PPSSPPpath / "installed.txt");
1174 		installed_ = false;
1175 		g_Config.memStickDirectory = PPSSPPpath / "memstick";
1176 	} else {
1177 		FILE *f = File::OpenCFile(PPSSPPpath / "installed.txt", "wb");
1178 		if (f) {
1179 			fclose(f);
1180 		}
1181 
1182 		const std::string myDocsPath = W32Util::UserDocumentsPath() + "/PPSSPP/";
1183 		g_Config.memStickDirectory = Path(myDocsPath);
1184 		installed_ = true;
1185 	}
1186 	return UI::EVENT_DONE;
1187 }
1188 
OnSavePathOther(UI::EventParams & e)1189 UI::EventReturn GameSettingsScreen::OnSavePathOther(UI::EventParams &e) {
1190 	const Path &PPSSPPpath = File::GetExeDirectory();
1191 	if (otherinstalled_) {
1192 		auto di = GetI18NCategory("Dialog");
1193 		std::string folder = W32Util::BrowseForFolder(MainWindow::GetHWND(), di->T("Choose PPSSPP save folder"));
1194 		if (folder.size()) {
1195 			g_Config.memStickDirectory = Path(folder);
1196 			FILE *f = File::OpenCFile(PPSSPPpath / "installed.txt", "wb");
1197 			if (f) {
1198 				std::string utfstring("\xEF\xBB\xBF");
1199 				utfstring.append(folder);
1200 				fwrite(utfstring.c_str(), 1, utfstring.length(), f);
1201 				fclose(f);
1202 			}
1203 			installed_ = false;
1204 		}
1205 		else
1206 			otherinstalled_ = false;
1207 	}
1208 	else {
1209 		File::Delete(PPSSPPpath / "installed.txt");
1210 		SavePathInMyDocumentChoice->SetEnabled(true);
1211 		otherinstalled_ = false;
1212 		installed_ = false;
1213 		g_Config.memStickDirectory = PPSSPPpath / "memstick";
1214 	}
1215 	return UI::EVENT_DONE;
1216 }
1217 
1218 #endif
1219 
OnChangeBackground(UI::EventParams & e)1220 UI::EventReturn GameSettingsScreen::OnChangeBackground(UI::EventParams &e) {
1221 	const Path bgPng = GetSysDirectory(DIRECTORY_SYSTEM) / "background.png";
1222 	const Path bgJpg = GetSysDirectory(DIRECTORY_SYSTEM) / "background.jpg";
1223 
1224 	if (File::Exists(bgPng) || File::Exists(bgJpg)) {
1225 		File::Delete(bgPng);
1226 		File::Delete(bgJpg);
1227 
1228 		NativeMessageReceived("bgImage_updated", "");
1229 	} else {
1230 		if (System_GetPropertyBool(SYSPROP_HAS_IMAGE_BROWSER)) {
1231 			System_SendMessage("bgImage_browse", "");
1232 		}
1233 	}
1234 
1235 	// Change to a browse or clear button.
1236 	RecreateViews();
1237 	return UI::EVENT_DONE;
1238 }
1239 
OnFullscreenChange(UI::EventParams & e)1240 UI::EventReturn GameSettingsScreen::OnFullscreenChange(UI::EventParams &e) {
1241 	System_SendMessage("toggle_fullscreen", g_Config.bFullScreen ? "1" : "0");
1242 	return UI::EVENT_DONE;
1243 }
1244 
OnDisplayLayoutEditor(UI::EventParams & e)1245 UI::EventReturn GameSettingsScreen::OnDisplayLayoutEditor(UI::EventParams &e) {
1246 	screenManager()->push(new DisplayLayoutScreen());
1247 	return UI::EVENT_DONE;
1248 };
1249 
OnResolutionChange(UI::EventParams & e)1250 UI::EventReturn GameSettingsScreen::OnResolutionChange(UI::EventParams &e) {
1251 	if (g_Config.iAndroidHwScale == 1) {
1252 		RecreateActivity();
1253 	}
1254 	Reporting::UpdateConfig();
1255 	return UI::EVENT_DONE;
1256 }
1257 
OnHwScaleChange(UI::EventParams & e)1258 UI::EventReturn GameSettingsScreen::OnHwScaleChange(UI::EventParams &e) {
1259 	RecreateActivity();
1260 	return UI::EVENT_DONE;
1261 }
1262 
OnDumpNextFrameToLog(UI::EventParams & e)1263 UI::EventReturn GameSettingsScreen::OnDumpNextFrameToLog(UI::EventParams &e) {
1264 	if (gpu) {
1265 		gpu->DumpNextFrame();
1266 	}
1267 	return UI::EVENT_DONE;
1268 }
1269 
update()1270 void GameSettingsScreen::update() {
1271 	UIScreen::update();
1272 
1273 	bool vertical = UseVerticalLayout();
1274 	if (vertical != lastVertical_) {
1275 		RecreateViews();
1276 		lastVertical_ = vertical;
1277 	}
1278 }
1279 
onFinish(DialogResult result)1280 void GameSettingsScreen::onFinish(DialogResult result) {
1281 	if (g_Config.bEnableSound) {
1282 		if (PSP_IsInited() && !IsAudioInitialised())
1283 			Audio_Init();
1284 	}
1285 
1286 	Reporting::Enable(enableReports_, "report.ppsspp.org");
1287 	Reporting::UpdateConfig();
1288 	if (!g_Config.Save("GameSettingsScreen::onFinish")) {
1289 		System_SendMessage("toast", "Failed to save settings!\nCheck permissions, or try to restart the device.");
1290 	}
1291 
1292 	if (editThenRestore_) {
1293 		// In case we didn't have the title yet before, try again.
1294 		std::shared_ptr<GameInfo> info = g_gameInfoCache->GetInfo(nullptr, gamePath_, 0);
1295 		g_Config.changeGameSpecific(gameID_, info->GetTitle());
1296 		g_Config.unloadGameConfig();
1297 	}
1298 
1299 	host->UpdateUI();
1300 
1301 	KeyMap::UpdateNativeMenuKeys();
1302 
1303 	// Wipe some caches after potentially changing settings.
1304 	NativeMessageReceived("gpu_resized", "");
1305 	NativeMessageReceived("gpu_clearCache", "");
1306 }
1307 
sendMessage(const char * message,const char * value)1308 void GameSettingsScreen::sendMessage(const char *message, const char *value) {
1309 	UIDialogScreenWithGameBackground::sendMessage(message, value);
1310 	if (!strcmp(message, "postshader_updated")) {
1311 		g_Config.bShaderChainRequires60FPS = PostShaderChainRequires60FPS(GetFullPostShadersChain(g_Config.vPostShaderNames));
1312 		RecreateViews();
1313 	}
1314 }
1315 
dialogFinished(const Screen * dialog,DialogResult result)1316 void GameSettingsScreen::dialogFinished(const Screen *dialog, DialogResult result) {
1317 	if (result == DialogResult::DR_OK) {
1318 		g_Config.iFpsLimit1 = iAlternateSpeedPercent1_ < 0 ? -1 : (iAlternateSpeedPercent1_ * 60) / 100;
1319 		g_Config.iFpsLimit2 = iAlternateSpeedPercent2_ < 0 ? -1 : (iAlternateSpeedPercent2_ * 60) / 100;
1320 
1321 		RecreateViews();
1322 	}
1323 }
1324 
RecreateViews()1325 void GameSettingsScreen::RecreateViews() {
1326 	oldSettingInfo_ = settingInfo_->GetText();
1327 	UIScreen::RecreateViews();
1328 }
1329 
CallbackMemstickFolder(bool yes)1330 void GameSettingsScreen::CallbackMemstickFolder(bool yes) {
1331 	auto sy = GetI18NCategory("System");
1332 
1333 	if (yes) {
1334 		Path memstickDirFile = g_Config.internalDataDirectory / "memstick_dir.txt";
1335 		std::string testWriteFile = pendingMemstickFolder_ + "/.write_verify_file";
1336 
1337 		// Already, create away.
1338 		if (!File::Exists(Path(pendingMemstickFolder_))) {
1339 			File::CreateFullPath(Path(pendingMemstickFolder_));
1340 		}
1341 		if (!File::WriteDataToFile(true, "1", 1, Path(testWriteFile))) {
1342 			settingInfo_->Show(sy->T("ChangingMemstickPathInvalid", "That path couldn't be used to save Memory Stick files."), nullptr);
1343 			return;
1344 		}
1345 		File::Delete(Path(testWriteFile));
1346 
1347 		if (!File::WriteDataToFile(true, pendingMemstickFolder_.c_str(), (unsigned int)pendingMemstickFolder_.size(), memstickDirFile)) {
1348 			WARN_LOG(SYSTEM, "Failed to write memstick folder to '%s'", memstickDirFile.c_str());
1349 		} else {
1350 			// Save so the settings, at least, are transferred.
1351 			g_Config.memStickDirectory = Path(pendingMemstickFolder_);
1352 			g_Config.Save("MemstickPathChanged");
1353 		}
1354 		screenManager()->RecreateAllViews();
1355 	}
1356 }
1357 
TriggerRestart(const char * why)1358 void GameSettingsScreen::TriggerRestart(const char *why) {
1359 	// Extra save here to make sure the choice really gets saved even if there are shutdown bugs in
1360 	// the GPU backend code.
1361 	g_Config.Save(why);
1362 	std::string param = "--gamesettings";
1363 	if (editThenRestore_) {
1364 		// We won't pass the gameID, so don't resume back into settings.
1365 		param = "";
1366 	} else if (!gamePath_.empty()) {
1367 		param += " \"" + ReplaceAll(ReplaceAll(gamePath_.ToString(), "\\", "\\\\"), "\"", "\\\"") + "\"";
1368 	}
1369 	// Make sure the new instance is considered the first.
1370 	ShutdownInstanceCounter();
1371 	System_SendMessage("graphics_restart", param.c_str());
1372 }
1373 
CallbackRenderingBackend(bool yes)1374 void GameSettingsScreen::CallbackRenderingBackend(bool yes) {
1375 	// If the user ends up deciding not to restart, set the config back to the current backend
1376 	// so it doesn't get switched by accident.
1377 	if (yes) {
1378 		TriggerRestart("GameSettingsScreen::RenderingBackendYes");
1379 	} else {
1380 		g_Config.iGPUBackend = (int)GetGPUBackend();
1381 	}
1382 }
1383 
CallbackRenderingDevice(bool yes)1384 void GameSettingsScreen::CallbackRenderingDevice(bool yes) {
1385 	// If the user ends up deciding not to restart, set the config back to the current backend
1386 	// so it doesn't get switched by accident.
1387 	if (yes) {
1388 		TriggerRestart("GameSettingsScreen::RenderingDeviceYes");
1389 	} else {
1390 		std::string *deviceNameSetting = GPUDeviceNameSetting();
1391 		if (deviceNameSetting)
1392 			*deviceNameSetting = GetGPUBackendDevice();
1393 		// Needed to redraw the setting.
1394 		RecreateViews();
1395 	}
1396 }
1397 
CallbackInflightFrames(bool yes)1398 void GameSettingsScreen::CallbackInflightFrames(bool yes) {
1399 	if (yes) {
1400 		TriggerRestart("GameSettingsScreen::InflightFramesYes");
1401 	} else {
1402 		g_Config.iInflightFrames = prevInflightFrames_;
1403 	}
1404 }
1405 
OnRenderingBackend(UI::EventParams & e)1406 UI::EventReturn GameSettingsScreen::OnRenderingBackend(UI::EventParams &e) {
1407 	auto di = GetI18NCategory("Dialog");
1408 
1409 	// It only makes sense to show the restart prompt if the backend was actually changed.
1410 	if (g_Config.iGPUBackend != (int)GetGPUBackend()) {
1411 		screenManager()->push(new PromptScreen(di->T("ChangingGPUBackends", "Changing GPU backends requires PPSSPP to restart. Restart now?"), di->T("Yes"), di->T("No"),
1412 			std::bind(&GameSettingsScreen::CallbackRenderingBackend, this, std::placeholders::_1)));
1413 	}
1414 	return UI::EVENT_DONE;
1415 }
1416 
OnRenderingDevice(UI::EventParams & e)1417 UI::EventReturn GameSettingsScreen::OnRenderingDevice(UI::EventParams &e) {
1418 	auto di = GetI18NCategory("Dialog");
1419 
1420 	// It only makes sense to show the restart prompt if the device was actually changed.
1421 	std::string *deviceNameSetting = GPUDeviceNameSetting();
1422 	if (deviceNameSetting && *deviceNameSetting != GetGPUBackendDevice()) {
1423 		screenManager()->push(new PromptScreen(di->T("ChangingGPUBackends", "Changing GPU backends requires PPSSPP to restart. Restart now?"), di->T("Yes"), di->T("No"),
1424 			std::bind(&GameSettingsScreen::CallbackRenderingDevice, this, std::placeholders::_1)));
1425 	}
1426 	return UI::EVENT_DONE;
1427 }
1428 
OnInflightFramesChoice(UI::EventParams & e)1429 UI::EventReturn GameSettingsScreen::OnInflightFramesChoice(UI::EventParams &e) {
1430 	auto di = GetI18NCategory("Dialog");
1431 	if (g_Config.iInflightFrames != prevInflightFrames_) {
1432 		screenManager()->push(new PromptScreen(di->T("ChangingInflightFrames", "Changing graphics command buffering requires PPSSPP to restart. Restart now?"), di->T("Yes"), di->T("No"),
1433 			std::bind(&GameSettingsScreen::CallbackInflightFrames, this, std::placeholders::_1)));
1434 	}
1435 	return UI::EVENT_DONE;
1436 }
1437 
OnCameraDeviceChange(UI::EventParams & e)1438 UI::EventReturn GameSettingsScreen::OnCameraDeviceChange(UI::EventParams& e) {
1439 	Camera::onCameraDeviceChange();
1440 	return UI::EVENT_DONE;
1441 }
1442 
OnMicDeviceChange(UI::EventParams & e)1443 UI::EventReturn GameSettingsScreen::OnMicDeviceChange(UI::EventParams& e) {
1444 	Microphone::onMicDeviceChange();
1445 	return UI::EVENT_DONE;
1446 }
1447 
OnAudioDevice(UI::EventParams & e)1448 UI::EventReturn GameSettingsScreen::OnAudioDevice(UI::EventParams &e) {
1449 	auto a = GetI18NCategory("Audio");
1450 	if (g_Config.sAudioDevice == a->T("Auto")) {
1451 		g_Config.sAudioDevice.clear();
1452 	}
1453 	System_SendMessage("audio_resetDevice", "");
1454 	return UI::EVENT_DONE;
1455 }
1456 
OnChangeQuickChat0(UI::EventParams & e)1457 UI::EventReturn GameSettingsScreen::OnChangeQuickChat0(UI::EventParams &e) {
1458 #if PPSSPP_PLATFORM(WINDOWS) || defined(USING_QT_UI) || PPSSPP_PLATFORM(ANDROID)
1459 	auto n = GetI18NCategory("Networking");
1460 	System_InputBoxGetString(n->T("Enter Quick Chat 1"), g_Config.sQuickChat0, [](bool result, const std::string &value) {
1461 		if (result) {
1462 			g_Config.sQuickChat0 = value;
1463 		}
1464 	});
1465 #endif
1466 	return UI::EVENT_DONE;
1467 }
1468 
OnChangeQuickChat1(UI::EventParams & e)1469 UI::EventReturn GameSettingsScreen::OnChangeQuickChat1(UI::EventParams &e) {
1470 #if PPSSPP_PLATFORM(WINDOWS) || defined(USING_QT_UI) || PPSSPP_PLATFORM(ANDROID)
1471 	auto n = GetI18NCategory("Networking");
1472 	System_InputBoxGetString(n->T("Enter Quick Chat 2"), g_Config.sQuickChat1, [](bool result, const std::string &value) {
1473 		if (result) {
1474 			g_Config.sQuickChat1 = value;
1475 		}
1476 	});
1477 #endif
1478 	return UI::EVENT_DONE;
1479 }
1480 
OnChangeQuickChat2(UI::EventParams & e)1481 UI::EventReturn GameSettingsScreen::OnChangeQuickChat2(UI::EventParams &e) {
1482 #if PPSSPP_PLATFORM(WINDOWS) || defined(USING_QT_UI) || PPSSPP_PLATFORM(ANDROID)
1483 	auto n = GetI18NCategory("Networking");
1484 	System_InputBoxGetString(n->T("Enter Quick Chat 3"), g_Config.sQuickChat2, [](bool result, const std::string &value) {
1485 		if (result) {
1486 			g_Config.sQuickChat2 = value;
1487 		}
1488 	});
1489 #endif
1490 	return UI::EVENT_DONE;
1491 }
1492 
OnChangeQuickChat3(UI::EventParams & e)1493 UI::EventReturn GameSettingsScreen::OnChangeQuickChat3(UI::EventParams &e) {
1494 #if PPSSPP_PLATFORM(WINDOWS) || defined(USING_QT_UI) || PPSSPP_PLATFORM(ANDROID)
1495 	auto n = GetI18NCategory("Networking");
1496 	System_InputBoxGetString(n->T("Enter Quick Chat 4"), g_Config.sQuickChat3, [](bool result, const std::string &value) {
1497 		if (result) {
1498 			g_Config.sQuickChat3 = value;
1499 		}
1500 	});
1501 #endif
1502 	return UI::EVENT_DONE;
1503 }
1504 
OnChangeQuickChat4(UI::EventParams & e)1505 UI::EventReturn GameSettingsScreen::OnChangeQuickChat4(UI::EventParams &e) {
1506 #if PPSSPP_PLATFORM(WINDOWS) || defined(USING_QT_UI) || PPSSPP_PLATFORM(ANDROID)
1507 	auto n = GetI18NCategory("Networking");
1508 	System_InputBoxGetString(n->T("Enter Quick Chat 5"), g_Config.sQuickChat4, [](bool result, const std::string &value) {
1509 		if (result) {
1510 			g_Config.sQuickChat4 = value;
1511 		}
1512 	});
1513 #endif
1514 	return UI::EVENT_DONE;
1515 }
1516 
OnChangeNickname(UI::EventParams & e)1517 UI::EventReturn GameSettingsScreen::OnChangeNickname(UI::EventParams &e) {
1518 #if PPSSPP_PLATFORM(WINDOWS) || defined(USING_QT_UI) || PPSSPP_PLATFORM(ANDROID)
1519 	auto n = GetI18NCategory("Networking");
1520 	System_InputBoxGetString(n->T("Enter a new PSP nickname"), g_Config.sNickName, [](bool result, const std::string &value) {
1521 		if (result) {
1522 			g_Config.sNickName = StripSpaces(value);
1523 		}
1524 	});
1525 #endif
1526 	return UI::EVENT_DONE;
1527 }
1528 
OnChangeproAdhocServerAddress(UI::EventParams & e)1529 UI::EventReturn GameSettingsScreen::OnChangeproAdhocServerAddress(UI::EventParams &e) {
1530 	auto n = GetI18NCategory("Networking");
1531 
1532 	screenManager()->push(new HostnameSelectScreen(&g_Config.proAdhocServer, n->T("proAdhocServer Address:")));
1533 
1534 	return UI::EVENT_DONE;
1535 }
1536 
OnChangeMacAddress(UI::EventParams & e)1537 UI::EventReturn GameSettingsScreen::OnChangeMacAddress(UI::EventParams &e) {
1538 	g_Config.sMACAddress = CreateRandMAC();
1539 
1540 	return UI::EVENT_DONE;
1541 }
1542 
OnLanguage(UI::EventParams & e)1543 UI::EventReturn GameSettingsScreen::OnLanguage(UI::EventParams &e) {
1544 	auto dev = GetI18NCategory("Developer");
1545 	auto langScreen = new NewLanguageScreen(dev->T("Language"));
1546 	langScreen->OnChoice.Handle(this, &GameSettingsScreen::OnLanguageChange);
1547 	if (e.v)
1548 		langScreen->SetPopupOrigin(e.v);
1549 	screenManager()->push(langScreen);
1550 	return UI::EVENT_DONE;
1551 }
1552 
OnLanguageChange(UI::EventParams & e)1553 UI::EventReturn GameSettingsScreen::OnLanguageChange(UI::EventParams &e) {
1554 	screenManager()->RecreateAllViews();
1555 
1556 	if (host) {
1557 		host->UpdateUI();
1558 	}
1559 	return UI::EVENT_DONE;
1560 }
1561 
OnPostProcShaderChange(UI::EventParams & e)1562 UI::EventReturn GameSettingsScreen::OnPostProcShaderChange(UI::EventParams &e) {
1563 	g_Config.vPostShaderNames.erase(std::remove(g_Config.vPostShaderNames.begin(), g_Config.vPostShaderNames.end(), "Off"), g_Config.vPostShaderNames.end());
1564 
1565 	NativeMessageReceived("gpu_resized", "");
1566 	NativeMessageReceived("postshader_updated", "");
1567 	return UI::EVENT_DONE;
1568 }
1569 
OnTextureShader(UI::EventParams & e)1570 UI::EventReturn GameSettingsScreen::OnTextureShader(UI::EventParams &e) {
1571 	auto gr = GetI18NCategory("Graphics");
1572 	auto shaderScreen = new TextureShaderScreen(gr->T("Texture Shader"));
1573 	shaderScreen->OnChoice.Handle(this, &GameSettingsScreen::OnTextureShaderChange);
1574 	if (e.v)
1575 		shaderScreen->SetPopupOrigin(e.v);
1576 	screenManager()->push(shaderScreen);
1577 	return UI::EVENT_DONE;
1578 }
1579 
OnTextureShaderChange(UI::EventParams & e)1580 UI::EventReturn GameSettingsScreen::OnTextureShaderChange(UI::EventParams &e) {
1581 	NativeMessageReceived("gpu_resized", "");
1582 	RecreateViews(); // Update setting name
1583 	g_Config.bTexHardwareScaling = g_Config.sTextureShaderName != "Off";
1584 	return UI::EVENT_DONE;
1585 }
1586 
OnDeveloperTools(UI::EventParams & e)1587 UI::EventReturn GameSettingsScreen::OnDeveloperTools(UI::EventParams &e) {
1588 	screenManager()->push(new DeveloperToolsScreen());
1589 	return UI::EVENT_DONE;
1590 }
1591 
OnRemoteISO(UI::EventParams & e)1592 UI::EventReturn GameSettingsScreen::OnRemoteISO(UI::EventParams &e) {
1593 	screenManager()->push(new RemoteISOScreen());
1594 	return UI::EVENT_DONE;
1595 }
1596 
OnControlMapping(UI::EventParams & e)1597 UI::EventReturn GameSettingsScreen::OnControlMapping(UI::EventParams &e) {
1598 	screenManager()->push(new ControlMappingScreen());
1599 	return UI::EVENT_DONE;
1600 }
1601 
OnCalibrateAnalogs(UI::EventParams & e)1602 UI::EventReturn GameSettingsScreen::OnCalibrateAnalogs(UI::EventParams &e) {
1603 	screenManager()->push(new AnalogSetupScreen());
1604 	return UI::EVENT_DONE;
1605 }
1606 
OnTouchControlLayout(UI::EventParams & e)1607 UI::EventReturn GameSettingsScreen::OnTouchControlLayout(UI::EventParams &e) {
1608 	screenManager()->push(new TouchControlLayoutScreen());
1609 	return UI::EVENT_DONE;
1610 }
1611 
1612 //when the tilt event type is modified, we need to reset all tilt settings.
1613 //refer to the ResetTiltEvents() function for a detailed explanation.
OnTiltTypeChange(UI::EventParams & e)1614 UI::EventReturn GameSettingsScreen::OnTiltTypeChange(UI::EventParams &e) {
1615 	TiltEventProcessor::ResetTiltEvents();
1616 	return UI::EVENT_DONE;
1617 };
1618 
OnTiltCustomize(UI::EventParams & e)1619 UI::EventReturn GameSettingsScreen::OnTiltCustomize(UI::EventParams &e) {
1620 	screenManager()->push(new TiltAnalogSettingsScreen());
1621 	return UI::EVENT_DONE;
1622 };
1623 
OnSavedataManager(UI::EventParams & e)1624 UI::EventReturn GameSettingsScreen::OnSavedataManager(UI::EventParams &e) {
1625 	auto saveData = new SavedataScreen(Path());
1626 	screenManager()->push(saveData);
1627 	return UI::EVENT_DONE;
1628 }
1629 
OnSysInfo(UI::EventParams & e)1630 UI::EventReturn GameSettingsScreen::OnSysInfo(UI::EventParams &e) {
1631 	screenManager()->push(new SystemInfoScreen());
1632 	return UI::EVENT_DONE;
1633 }
1634 
CreateViews()1635 void DeveloperToolsScreen::CreateViews() {
1636 	using namespace UI;
1637 	root_ = new LinearLayout(ORIENT_VERTICAL, new LayoutParams(FILL_PARENT, FILL_PARENT));
1638 	ScrollView *settingsScroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(1.0f));
1639 	settingsScroll->SetTag("DevToolsSettings");
1640 	root_->Add(settingsScroll);
1641 
1642 	auto di = GetI18NCategory("Dialog");
1643 	auto dev = GetI18NCategory("Developer");
1644 	auto gr = GetI18NCategory("Graphics");
1645 	auto a = GetI18NCategory("Audio");
1646 	auto sy = GetI18NCategory("System");
1647 
1648 	AddStandardBack(root_);
1649 
1650 	LinearLayout *list = settingsScroll->Add(new LinearLayoutList(ORIENT_VERTICAL, new LinearLayoutParams(1.0f)));
1651 	list->SetSpacing(0);
1652 	list->Add(new ItemHeader(sy->T("General")));
1653 
1654 	bool canUseJit = true;
1655 	// iOS can now use JIT on all modes, apparently.
1656 	// The bool may come in handy for future non-jit platforms though (UWP XB1?)
1657 
1658 	static const char *cpuCores[] = {"Interpreter", "Dynarec (JIT)", "IR Interpreter"};
1659 	PopupMultiChoice *core = list->Add(new PopupMultiChoice(&g_Config.iCpuCore, gr->T("CPU Core"), cpuCores, 0, ARRAY_SIZE(cpuCores), sy->GetName(), screenManager()));
1660 	core->OnChoice.Handle(this, &DeveloperToolsScreen::OnJitAffectingSetting);
1661 	if (!canUseJit) {
1662 		core->HideChoice(1);
1663 	}
1664 
1665 	list->Add(new Choice(dev->T("JIT debug tools")))->OnClick.Handle(this, &DeveloperToolsScreen::OnJitDebugTools);
1666 	list->Add(new CheckBox(&g_Config.bShowDeveloperMenu, dev->T("Show Developer Menu")));
1667 	list->Add(new CheckBox(&g_Config.bDumpDecryptedEboot, dev->T("Dump Decrypted Eboot", "Dump Decrypted EBOOT.BIN (If Encrypted) When Booting Game")));
1668 
1669 #if !PPSSPP_PLATFORM(UWP)
1670 	Choice *cpuTests = new Choice(dev->T("Run CPU Tests"));
1671 	list->Add(cpuTests)->OnClick.Handle(this, &DeveloperToolsScreen::OnRunCPUTests);
1672 
1673 	cpuTests->SetEnabled(TestsAvailable());
1674 #endif
1675 	// For now, we only implement GPU driver tests for Vulkan and OpenGL. This is simply
1676 	// because the D3D drivers are generally solid enough to not need this type of investigation.
1677 	if (g_Config.iGPUBackend == (int)GPUBackend::VULKAN || g_Config.iGPUBackend == (int)GPUBackend::OPENGL) {
1678 		list->Add(new Choice(dev->T("GPU Driver Test")))->OnClick.Handle(this, &DeveloperToolsScreen::OnGPUDriverTest);
1679 	}
1680 	list->Add(new CheckBox(&g_Config.bVendorBugChecksEnabled, dev->T("Enable driver bug workarounds")));
1681 	list->Add(new Choice(dev->T("Framedump tests")))->OnClick.Handle(this, &DeveloperToolsScreen::OnFramedumpTest);
1682 	list->Add(new Choice(dev->T("Touchscreen Test")))->OnClick.Handle(this, &DeveloperToolsScreen::OnTouchscreenTest);
1683 
1684 	allowDebugger_ = !WebServerStopped(WebServerFlags::DEBUGGER);
1685 	canAllowDebugger_ = !WebServerStopping(WebServerFlags::DEBUGGER);
1686 	CheckBox *allowDebugger = new CheckBox(&allowDebugger_, dev->T("Allow remote debugger"));
1687 	list->Add(allowDebugger)->OnClick.Handle(this, &DeveloperToolsScreen::OnRemoteDebugger);
1688 	allowDebugger->SetEnabledPtr(&canAllowDebugger_);
1689 
1690 	list->Add(new CheckBox(&g_Config.bShowOnScreenMessages, dev->T("Show on-screen messages")));
1691 	list->Add(new CheckBox(&g_Config.bEnableLogging, dev->T("Enable Logging")))->OnClick.Handle(this, &DeveloperToolsScreen::OnLoggingChanged);
1692 	list->Add(new CheckBox(&g_Config.bLogFrameDrops, dev->T("Log Dropped Frame Statistics")));
1693 	list->Add(new Choice(dev->T("Logging Channels")))->OnClick.Handle(this, &DeveloperToolsScreen::OnLogConfig);
1694 	list->Add(new ItemHeader(dev->T("Language")));
1695 	list->Add(new Choice(dev->T("Load language ini")))->OnClick.Handle(this, &DeveloperToolsScreen::OnLoadLanguageIni);
1696 	list->Add(new Choice(dev->T("Save language ini")))->OnClick.Handle(this, &DeveloperToolsScreen::OnSaveLanguageIni);
1697 	list->Add(new ItemHeader(dev->T("Texture Replacement")));
1698 	list->Add(new CheckBox(&g_Config.bSaveNewTextures, dev->T("Save new textures")));
1699 	list->Add(new CheckBox(&g_Config.bReplaceTextures, dev->T("Replace textures")));
1700 
1701 	// Makes it easy to get savestates out of an iOS device. The file listing shown in MacOS doesn't allow
1702 	// you to descend into directories.
1703 #if PPSSPP_PLATFORM(IOS)
1704 	list->Add(new Choice(dev->T("Copy savestates to memstick root")))->OnClick.Handle(this, &DeveloperToolsScreen::OnCopyStatesToRoot);
1705 #endif
1706 
1707 #if !defined(MOBILE_DEVICE)
1708 	Choice *createTextureIni = list->Add(new Choice(dev->T("Create/Open textures.ini file for current game")));
1709 	createTextureIni->OnClick.Handle(this, &DeveloperToolsScreen::OnOpenTexturesIniFile);
1710 	if (!PSP_IsInited()) {
1711 		createTextureIni->SetEnabled(false);
1712 	}
1713 #endif
1714 }
1715 
onFinish(DialogResult result)1716 void DeveloperToolsScreen::onFinish(DialogResult result) {
1717 	g_Config.Save("DeveloperToolsScreen::onFinish");
1718 }
1719 
CallbackRestoreDefaults(bool yes)1720 void GameSettingsScreen::CallbackRestoreDefaults(bool yes) {
1721 	if (yes)
1722 		g_Config.RestoreDefaults();
1723 	host->UpdateUI();
1724 }
1725 
OnRestoreDefaultSettings(UI::EventParams & e)1726 UI::EventReturn GameSettingsScreen::OnRestoreDefaultSettings(UI::EventParams &e) {
1727 	auto dev = GetI18NCategory("Developer");
1728 	auto di = GetI18NCategory("Dialog");
1729 	if (g_Config.bGameSpecific)
1730 	{
1731 		screenManager()->push(
1732 			new PromptScreen(dev->T("RestoreGameDefaultSettings", "Are you sure you want to restore the game-specific settings back to the ppsspp defaults?\n"), di->T("OK"), di->T("Cancel"),
1733 			std::bind(&GameSettingsScreen::CallbackRestoreDefaults, this, std::placeholders::_1)));
1734 	}
1735 	else
1736 	{
1737 		screenManager()->push(
1738 			new PromptScreen(dev->T("RestoreDefaultSettings", "Are you sure you want to restore all settings(except control mapping)\nback to their defaults?\nYou can't undo this.\nPlease restart PPSSPP after restoring settings."), di->T("OK"), di->T("Cancel"),
1739 			std::bind(&GameSettingsScreen::CallbackRestoreDefaults, this, std::placeholders::_1)));
1740 	}
1741 
1742 	return UI::EVENT_DONE;
1743 }
1744 
OnLoggingChanged(UI::EventParams & e)1745 UI::EventReturn DeveloperToolsScreen::OnLoggingChanged(UI::EventParams &e) {
1746 	host->ToggleDebugConsoleVisibility();
1747 	return UI::EVENT_DONE;
1748 }
1749 
OnRunCPUTests(UI::EventParams & e)1750 UI::EventReturn DeveloperToolsScreen::OnRunCPUTests(UI::EventParams &e) {
1751 #if !PPSSPP_PLATFORM(UWP)
1752 	RunTests();
1753 #endif
1754 	return UI::EVENT_DONE;
1755 }
1756 
OnSaveLanguageIni(UI::EventParams & e)1757 UI::EventReturn DeveloperToolsScreen::OnSaveLanguageIni(UI::EventParams &e) {
1758 	i18nrepo.SaveIni(g_Config.sLanguageIni);
1759 	return UI::EVENT_DONE;
1760 }
1761 
OnLoadLanguageIni(UI::EventParams & e)1762 UI::EventReturn DeveloperToolsScreen::OnLoadLanguageIni(UI::EventParams &e) {
1763 	i18nrepo.LoadIni(g_Config.sLanguageIni);
1764 	return UI::EVENT_DONE;
1765 }
1766 
OnOpenTexturesIniFile(UI::EventParams & e)1767 UI::EventReturn DeveloperToolsScreen::OnOpenTexturesIniFile(UI::EventParams &e) {
1768 	std::string gameID = g_paramSFO.GetDiscID();
1769 	Path generatedFilename;
1770 	if (TextureReplacer::GenerateIni(gameID, generatedFilename)) {
1771 		File::OpenFileInEditor(generatedFilename);
1772 	}
1773 	return UI::EVENT_DONE;
1774 }
1775 
OnLogConfig(UI::EventParams & e)1776 UI::EventReturn DeveloperToolsScreen::OnLogConfig(UI::EventParams &e) {
1777 	screenManager()->push(new LogConfigScreen());
1778 	return UI::EVENT_DONE;
1779 }
1780 
OnJitDebugTools(UI::EventParams & e)1781 UI::EventReturn DeveloperToolsScreen::OnJitDebugTools(UI::EventParams &e) {
1782 	screenManager()->push(new JitDebugScreen());
1783 	return UI::EVENT_DONE;
1784 }
1785 
OnGPUDriverTest(UI::EventParams & e)1786 UI::EventReturn DeveloperToolsScreen::OnGPUDriverTest(UI::EventParams &e) {
1787 	screenManager()->push(new GPUDriverTestScreen());
1788 	return UI::EVENT_DONE;
1789 }
1790 
OnFramedumpTest(UI::EventParams & e)1791 UI::EventReturn DeveloperToolsScreen::OnFramedumpTest(UI::EventParams &e) {
1792 	screenManager()->push(new FrameDumpTestScreen());
1793 	return UI::EVENT_DONE;
1794 }
1795 
OnTouchscreenTest(UI::EventParams & e)1796 UI::EventReturn DeveloperToolsScreen::OnTouchscreenTest(UI::EventParams &e) {
1797 	screenManager()->push(new TouchTestScreen());
1798 	return UI::EVENT_DONE;
1799 }
1800 
OnJitAffectingSetting(UI::EventParams & e)1801 UI::EventReturn DeveloperToolsScreen::OnJitAffectingSetting(UI::EventParams &e) {
1802 	NativeMessageReceived("clear jit", "");
1803 	return UI::EVENT_DONE;
1804 }
1805 
OnCopyStatesToRoot(UI::EventParams & e)1806 UI::EventReturn DeveloperToolsScreen::OnCopyStatesToRoot(UI::EventParams &e) {
1807 	Path savestate_dir = GetSysDirectory(DIRECTORY_SAVESTATE);
1808 	Path root_dir = GetSysDirectory(DIRECTORY_MEMSTICK_ROOT);
1809 
1810 	std::vector<File::FileInfo> files;
1811 	GetFilesInDir(savestate_dir, &files, nullptr, 0);
1812 
1813 	for (const File::FileInfo &file : files) {
1814 		Path src = file.fullName;
1815 		Path dst = root_dir / file.name;
1816 		INFO_LOG(SYSTEM, "Copying file '%s' to '%s'", src.c_str(), dst.c_str());
1817 		File::Copy(src, dst);
1818 	}
1819 
1820 	return UI::EVENT_DONE;
1821 }
1822 
OnRemoteDebugger(UI::EventParams & e)1823 UI::EventReturn DeveloperToolsScreen::OnRemoteDebugger(UI::EventParams &e) {
1824 	if (allowDebugger_) {
1825 		StartWebServer(WebServerFlags::DEBUGGER);
1826 	} else {
1827 		StopWebServer(WebServerFlags::DEBUGGER);
1828 	}
1829 	// Persist the setting.  Maybe should separate?
1830 	g_Config.bRemoteDebuggerOnStartup = allowDebugger_;
1831 	return UI::EVENT_CONTINUE;
1832 }
1833 
update()1834 void DeveloperToolsScreen::update() {
1835 	UIDialogScreenWithBackground::update();
1836 	allowDebugger_ = !WebServerStopped(WebServerFlags::DEBUGGER);
1837 	canAllowDebugger_ = !WebServerStopping(WebServerFlags::DEBUGGER);
1838 }
1839 
CreatePopupContents(UI::ViewGroup * parent)1840 void HostnameSelectScreen::CreatePopupContents(UI::ViewGroup *parent) {
1841 	using namespace UI;
1842 	auto sy = GetI18NCategory("System");
1843 	auto di = GetI18NCategory("Dialog");
1844 	auto n = GetI18NCategory("Networking");
1845 
1846 	LinearLayout *valueRow = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, FILL_PARENT, Margins(0, 0, 0, 10)));
1847 
1848 	addrView_ = new TextEdit(*value_, n->T("Hostname"), "");
1849 	addrView_->SetTextAlign(FLAG_DYNAMIC_ASCII);
1850 	valueRow->Add(addrView_);
1851 	parent->Add(valueRow);
1852 
1853 	LinearLayout *buttonsRow1 = new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT));
1854 	LinearLayout *buttonsRow2 = new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT));
1855 	parent->Add(buttonsRow1);
1856 	parent->Add(buttonsRow2);
1857 
1858 	buttonsRow1->Add(new Spacer(new LinearLayoutParams(1.0, G_LEFT)));
1859 	for (char c = '0'; c <= '9'; ++c) {
1860 		char label[] = { c, '\0' };
1861 		auto button = buttonsRow1->Add(new Button(label));
1862 		button->OnClick.Handle(this, &HostnameSelectScreen::OnNumberClick);
1863 		button->SetTag(label);
1864 	}
1865 	buttonsRow1->Add(new Button("."))->OnClick.Handle(this, &HostnameSelectScreen::OnPointClick);
1866 	buttonsRow1->Add(new Spacer(new LinearLayoutParams(1.0, G_RIGHT)));
1867 
1868 	buttonsRow2->Add(new Spacer(new LinearLayoutParams(1.0, G_LEFT)));
1869 #if PPSSPP_PLATFORM(ANDROID)
1870 	buttonsRow2->Add(new Button(di->T("Edit")))->OnClick.Handle(this, &HostnameSelectScreen::OnEditClick); // Since we don't have OnClick Event triggered from TextEdit's Touch().. here we go!
1871 #endif
1872 	buttonsRow2->Add(new Button(di->T("Delete")))->OnClick.Handle(this, &HostnameSelectScreen::OnDeleteClick);
1873 	buttonsRow2->Add(new Button(di->T("Delete all")))->OnClick.Handle(this, &HostnameSelectScreen::OnDeleteAllClick);
1874 	buttonsRow2->Add(new Button(di->T("Toggle List")))->OnClick.Handle(this, &HostnameSelectScreen::OnShowIPListClick);
1875 	buttonsRow2->Add(new Spacer(new LinearLayoutParams(1.0, G_RIGHT)));
1876 
1877 	std::vector<std::string> listIP = {"socom.cc", "myneighborsushicat.com", "localhost"}; // TODO: Add some saved recent history too?
1878 	net::GetIPList(listIP);
1879 	ipRows_ = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(1.0));
1880 	ScrollView* scrollView = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT));
1881 	LinearLayout* innerView = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT));
1882 	if (listIP.size() > 0) {
1883 		for (const auto& label : listIP) {
1884 			// Filter out IP prefixed with "127." and "169.254." also "0." since they can be rendundant or unusable
1885 			if (label.find("127.") != 0 && label.find("169.254.") != 0 && label.find("0.") != 0) {
1886 				auto button = innerView->Add(new Button(label, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)));
1887 				button->OnClick.Handle(this, &HostnameSelectScreen::OnIPClick);
1888 				button->SetTag(label);
1889 			}
1890 		}
1891 	}
1892 	scrollView->Add(innerView);
1893 	ipRows_->Add(scrollView);
1894 	ipRows_->SetVisibility(V_GONE);
1895 	parent->Add(ipRows_);
1896 	listIP.clear(); listIP.shrink_to_fit();
1897 
1898 	progressView_ = parent->Add(new TextView(n->T("Validating address..."), ALIGN_HCENTER, false, new LinearLayoutParams(Margins(0, 10, 0, 0))));
1899 	progressView_->SetVisibility(V_INVISIBLE);
1900 }
1901 
SendEditKey(int keyCode,int flags)1902 void HostnameSelectScreen::SendEditKey(int keyCode, int flags) {
1903 	auto oldView = UI::GetFocusedView();
1904 	UI::SetFocusedView(addrView_);
1905 	KeyInput fakeKey{ DEVICE_ID_KEYBOARD, keyCode, KEY_DOWN | flags };
1906 	addrView_->Key(fakeKey);
1907 	UI::SetFocusedView(oldView);
1908 }
1909 
OnNumberClick(UI::EventParams & e)1910 UI::EventReturn HostnameSelectScreen::OnNumberClick(UI::EventParams &e) {
1911 	std::string text = e.v ? e.v->Tag() : "";
1912 	if (text.length() == 1 && text[0] >= '0' && text[0] <= '9') {
1913 		SendEditKey(text[0], KEY_CHAR);
1914 	}
1915 	return UI::EVENT_DONE;
1916 }
1917 
OnPointClick(UI::EventParams & e)1918 UI::EventReturn HostnameSelectScreen::OnPointClick(UI::EventParams &e) {
1919 	SendEditKey('.', KEY_CHAR);
1920 	return UI::EVENT_DONE;
1921 }
1922 
OnDeleteClick(UI::EventParams & e)1923 UI::EventReturn HostnameSelectScreen::OnDeleteClick(UI::EventParams &e) {
1924 	SendEditKey(NKCODE_DEL);
1925 	return UI::EVENT_DONE;
1926 }
1927 
OnDeleteAllClick(UI::EventParams & e)1928 UI::EventReturn HostnameSelectScreen::OnDeleteAllClick(UI::EventParams &e) {
1929 	addrView_->SetText("");
1930 	return UI::EVENT_DONE;
1931 }
1932 
OnEditClick(UI::EventParams & e)1933 UI::EventReturn HostnameSelectScreen::OnEditClick(UI::EventParams& e) {
1934 	auto n = GetI18NCategory("Networking");
1935 #if PPSSPP_PLATFORM(ANDROID)
1936 	System_InputBoxGetString(n->T("proAdhocServer Address:"), addrView_->GetText(), [this](bool result, const std::string& value) {
1937 		if (result) {
1938 		    addrView_->SetText(value);
1939 		}
1940 	});
1941 #endif
1942 	return UI::EVENT_DONE;
1943 }
1944 
OnShowIPListClick(UI::EventParams & e)1945 UI::EventReturn HostnameSelectScreen::OnShowIPListClick(UI::EventParams& e) {
1946 	if (ipRows_->GetVisibility() == UI::V_GONE) {
1947 		ipRows_->SetVisibility(UI::V_VISIBLE);
1948 	}
1949 	else {
1950 		ipRows_->SetVisibility(UI::V_GONE);
1951 	}
1952 	return UI::EVENT_DONE;
1953 }
1954 
OnIPClick(UI::EventParams & e)1955 UI::EventReturn HostnameSelectScreen::OnIPClick(UI::EventParams& e) {
1956 	std::string text = e.v ? e.v->Tag() : "";
1957 	if (text.length() > 0) {
1958 		addrView_->SetText(text);
1959 		// TODO: Copy the IP to clipboard for the host to easily share their IP through chatting apps.
1960 		System_SendMessage("setclipboardtext", text.c_str()); // Doesn't seems to be working on windows (yet?)
1961 	}
1962 	return UI::EVENT_DONE;
1963 }
1964 
ResolverThread()1965 void HostnameSelectScreen::ResolverThread() {
1966 	std::unique_lock<std::mutex> guard(resolverLock_);
1967 
1968 	while (resolverState_ != ResolverState::QUIT) {
1969 		resolverCond_.wait(guard);
1970 
1971 		if (resolverState_ == ResolverState::QUEUED) {
1972 			resolverState_ = ResolverState::PROGRESS;
1973 
1974 			addrinfo *resolved = nullptr;
1975 			std::string err;
1976 			toResolveResult_ = net::DNSResolve(toResolve_, "80", &resolved, err);
1977 			if (resolved)
1978 				net::DNSResolveFree(resolved);
1979 
1980 			resolverState_ = ResolverState::READY;
1981 		}
1982 	}
1983 }
1984 
CanComplete(DialogResult result)1985 bool HostnameSelectScreen::CanComplete(DialogResult result) {
1986 	auto n = GetI18NCategory("Networking");
1987 
1988 	if (result != DR_OK)
1989 		return true;
1990 
1991 	std::string value = addrView_->GetText();
1992 	if (lastResolved_ == value) {
1993 		return true;
1994 	}
1995 
1996 	// Currently running.
1997 	if (resolverState_ == ResolverState::PROGRESS)
1998 		return false;
1999 
2000 	std::lock_guard<std::mutex> guard(resolverLock_);
2001 	switch (resolverState_) {
2002 	case ResolverState::PROGRESS:
2003 	case ResolverState::QUIT:
2004 		return false;
2005 
2006 	case ResolverState::QUEUED:
2007 	case ResolverState::WAITING:
2008 		break;
2009 
2010 	case ResolverState::READY:
2011 		if (toResolve_ == value) {
2012 			// Reset the state, nothing there now.
2013 			resolverState_ = ResolverState::WAITING;
2014 			toResolve_.clear();
2015 			lastResolved_ = value;
2016 			lastResolvedResult_ = toResolveResult_;
2017 
2018 			if (lastResolvedResult_) {
2019 				progressView_->SetVisibility(UI::V_INVISIBLE);
2020 			} else {
2021 				progressView_->SetText(n->T("Invalid IP or hostname"));
2022 				progressView_->SetTextColor(0xFF3030FF);
2023 				progressView_->SetVisibility(UI::V_VISIBLE);
2024 			}
2025 			return true;
2026 		}
2027 
2028 		// Throw away that last result, it was for a different value.
2029 		break;
2030 	}
2031 
2032 	resolverState_ = ResolverState::QUEUED;
2033 	toResolve_ = value;
2034 	resolverCond_.notify_one();
2035 
2036 	progressView_->SetText(n->T("Validating address..."));
2037 	progressView_->SetTextColor(0xFFFFFFFF);
2038 	progressView_->SetVisibility(UI::V_VISIBLE);
2039 
2040 	return false;
2041 }
2042 
OnCompleted(DialogResult result)2043 void HostnameSelectScreen::OnCompleted(DialogResult result) {
2044 	if (result == DR_OK)
2045 		*value_ = StripSpaces(addrView_->GetText());
2046 }
2047 
CreateViews()2048 void GestureMappingScreen::CreateViews() {
2049 	using namespace UI;
2050 
2051 	auto di = GetI18NCategory("Dialog");
2052 	auto co = GetI18NCategory("Controls");
2053 	auto mc = GetI18NCategory("MappableControls");
2054 
2055 	root_ = new AnchorLayout(new LayoutParams(FILL_PARENT, FILL_PARENT));
2056 	AddStandardBack(root_);
2057 	TabHolder *tabHolder = new TabHolder(ORIENT_VERTICAL, 200, new AnchorLayoutParams(10, 0, 10, 0, false));
2058 	root_->Add(tabHolder);
2059 	ScrollView *rightPanel = new ScrollView(ORIENT_VERTICAL);
2060 	tabHolder->AddTab(co->T("Gesture"), rightPanel);
2061 	LinearLayout *vert = rightPanel->Add(new LinearLayout(ORIENT_VERTICAL, new LayoutParams(FILL_PARENT, FILL_PARENT)));
2062 	vert->SetSpacing(0);
2063 
2064 	static const char *gestureButton[ARRAY_SIZE(GestureKey::keyList)+1];
2065 	gestureButton[0] = "None";
2066 	for (int i = 1; i < ARRAY_SIZE(gestureButton); ++i) {
2067 		gestureButton[i] = KeyMap::GetPspButtonNameCharPointer(GestureKey::keyList[i-1]);
2068 	}
2069 
2070 	vert->Add(new CheckBox(&g_Config.bGestureControlEnabled, co->T("Enable gesture control")));
2071 
2072 	vert->Add(new ItemHeader(co->T("Swipe")));
2073 	vert->Add(new PopupMultiChoice(&g_Config.iSwipeUp, mc->T("Swipe Up"), gestureButton, 0, ARRAY_SIZE(gestureButton), mc->GetName(), screenManager()))->SetEnabledPtr(&g_Config.bGestureControlEnabled);
2074 	vert->Add(new PopupMultiChoice(&g_Config.iSwipeDown, mc->T("Swipe Down"), gestureButton, 0, ARRAY_SIZE(gestureButton), mc->GetName(), screenManager()))->SetEnabledPtr(&g_Config.bGestureControlEnabled);
2075 	vert->Add(new PopupMultiChoice(&g_Config.iSwipeLeft, mc->T("Swipe Left"), gestureButton, 0, ARRAY_SIZE(gestureButton), mc->GetName(), screenManager()))->SetEnabledPtr(&g_Config.bGestureControlEnabled);
2076 	vert->Add(new PopupMultiChoice(&g_Config.iSwipeRight, mc->T("Swipe Right"), gestureButton, 0, ARRAY_SIZE(gestureButton), mc->GetName(), screenManager()))->SetEnabledPtr(&g_Config.bGestureControlEnabled);
2077 	vert->Add(new PopupSliderChoiceFloat(&g_Config.fSwipeSensitivity, 0.01f, 1.0f, co->T("Swipe sensitivity"), 0.01f, screenManager(), "x"))->SetEnabledPtr(&g_Config.bGestureControlEnabled);
2078 	vert->Add(new PopupSliderChoiceFloat(&g_Config.fSwipeSmoothing, 0.0f, 0.95f, co->T("Swipe smoothing"), 0.05f, screenManager(), "x"))->SetEnabledPtr(&g_Config.bGestureControlEnabled);
2079 
2080 	vert->Add(new ItemHeader(co->T("Double tap")));
2081 	vert->Add(new PopupMultiChoice(&g_Config.iDoubleTapGesture, mc->T("Double tap button"), gestureButton, 0, ARRAY_SIZE(gestureButton), mc->GetName(), screenManager()))->SetEnabledPtr(&g_Config.bGestureControlEnabled);
2082 }
2083 
2084