CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/UI/NativeApp.cpp
Views: 1401
// Copyright (c) 2012- PPSSPP Project.12// This program is free software: you can redistribute it and/or modify3// it under the terms of the GNU General Public License as published by4// the Free Software Foundation, version 2.0 or later versions.56// This program is distributed in the hope that it will be useful,7// but WITHOUT ANY WARRANTY; without even the implied warranty of8// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the9// GNU General Public License 2.0 for more details.1011// A copy of the GPL 2.0 should have been included with the program.12// If not, see http://www.gnu.org/licenses/1314// Official git repository and contact information can be found at15// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.1617// NativeApp implementation for platforms that will use that framework, like:18// Android, Linux, MacOSX.19//20// Native is a cross platform framework. It's not very mature and mostly21// just built according to the needs of my own apps.22//23// Windows has its own code that bypasses the framework entirely.2425#include "ppsspp_config.h"2627// Background worker threads should be spawned in NativeInit and joined28// in NativeShutdown.2930#include <locale.h>31#include <algorithm>32#include <cstdlib>33#include <memory>34#include <mutex>35#include <thread>3637#if defined(_WIN32)38#include "Windows/WindowsAudio.h"39#include "Windows/MainWindow.h"40#endif4142#if defined(_WIN32) && !PPSSPP_PLATFORM(UWP)43#include "Windows/CaptureDevice.h"44#endif4546#include "Common/Net/HTTPClient.h"47#include "Common/Net/Resolve.h"48#include "Common/Net/URL.h"49#include "Common/Render/TextureAtlas.h"50#include "Common/Render/Text/draw_text.h"51#include "Common/GPU/OpenGL/GLFeatures.h"52#include "Common/GPU/thin3d.h"53#include "Common/UI/UI.h"54#include "Common/UI/Screen.h"55#include "Common/UI/Context.h"56#include "Common/UI/View.h"57#include "Common/UI/IconCache.h"5859#include "android/jni/app-android.h"6061#include "Common/System/Display.h"62#include "Common/System/Request.h"63#include "Common/System/System.h"64#include "Common/System/OSD.h"65#include "Common/System/NativeApp.h"6667#include "Common/Data/Text/I18n.h"68#include "Common/Input/InputState.h"69#include "Common/Math/math_util.h"70#include "Common/Math/lin/matrix4x4.h"71#include "Common/Profiler/Profiler.h"72#include "Common/Data/Encoding/Utf8.h"73#include "Common/File/VFS/VFS.h"74#include "Common/File/VFS/ZipFileReader.h"75#include "Common/File/VFS/DirectoryReader.h"76#include "Common/CPUDetect.h"77#include "Common/File/FileUtil.h"78#include "Common/TimeUtil.h"79#include "Common/StringUtils.h"80#include "Common/Log/LogManager.h"81#include "Common/MemArena.h"82#include "Common/GraphicsContext.h"83#include "Common/OSVersion.h"84#include "Common/GPU/ShaderTranslation.h"85#include "Common/VR/PPSSPPVR.h"8687#include "Core/ControlMapper.h"88#include "Core/Config.h"89#include "Core/ConfigValues.h"90#include "Core/Core.h"91#include "Core/FileLoaders/DiskCachingFileLoader.h"92#include "Core/FrameTiming.h"93#include "Core/KeyMap.h"94#include "Core/Reporting.h"95#include "Core/RetroAchievements.h"96#include "Core/SaveState.h"97#include "Core/Screenshot.h"98#include "Core/System.h"99#include "Core/HLE/__sceAudio.h"100#include "Core/HLE/sceCtrl.h"101#include "Core/HLE/sceUsbCam.h"102#include "Core/HLE/sceUsbGps.h"103#include "Core/HLE/proAdhoc.h"104#include "Core/HW/MemoryStick.h"105#include "Core/Util/GameManager.h"106#include "Core/Util/PortManager.h"107#include "Core/Util/AudioFormat.h"108#include "Core/WebServer.h"109#include "Core/TiltEventProcessor.h"110#include "Core/ThreadPools.h"111112#include "GPU/GPUInterface.h"113#include "UI/AudioCommon.h"114#include "UI/BackgroundAudio.h"115#include "UI/ControlMappingScreen.h"116#include "UI/DevScreens.h"117#include "UI/DiscordIntegration.h"118#include "UI/EmuScreen.h"119#include "UI/GameInfoCache.h"120#include "UI/GameSettingsScreen.h"121#include "UI/GPUDriverTestScreen.h"122#include "UI/MiscScreens.h"123#include "UI/MemStickScreen.h"124#include "UI/OnScreenDisplay.h"125#include "UI/RemoteISOScreen.h"126#include "UI/Theme.h"127128#if defined(USING_QT_UI)129#include <QFontDatabase>130#endif131#if PPSSPP_PLATFORM(UWP)132#include <dwrite_3.h>133#include "UWP/UWPHelpers/InputHelpers.h"134#endif135#if PPSSPP_PLATFORM(ANDROID)136#include "android/jni/app-android.h"137#endif138139#if PPSSPP_ARCH(ARM) && defined(__ANDROID__)140#include "../../android/jni/ArmEmitterTest.h"141#elif PPSSPP_ARCH(ARM64) && defined(__ANDROID__)142#include "../../android/jni/Arm64EmitterTest.h"143#endif144145#if PPSSPP_PLATFORM(IOS)146#include "ios/iOSCoreAudio.h"147#elif defined(__APPLE__)148#include <mach-o/dyld.h>149#endif150151#if PPSSPP_PLATFORM(IOS) || PPSSPP_PLATFORM(MAC)152#include "UI/DarwinFileSystemServices.h"153#endif154155#if !defined(__LIBRETRO__)156#include "Core/Util/GameDB.h"157#endif158159#include <Core/HLE/Plugins.h>160161bool HandleGlobalMessage(UIMessage message, const std::string &value);162static void ProcessWheelRelease(InputKeyCode keyCode, double now, bool keyPress);163164ScreenManager *g_screenManager;165std::string config_filename;166167// Really need to clean this mess of globals up... but instead I add more :P168bool g_TakeScreenshot;169static bool isOuya;170static bool resized = false;171static bool restarting = false;172173static int renderCounter = 0;174175struct PendingMessage {176UIMessage message;177std::string value;178};179180static std::mutex pendingMutex;181static std::vector<PendingMessage> pendingMessages;182static Draw::DrawContext *g_draw;183static Draw::Pipeline *colorPipeline;184static Draw::Pipeline *texColorPipeline;185static UIContext *uiContext;186static int g_restartGraphics;187188#ifdef _WIN32189WindowsAudioBackend *winAudioBackend;190#endif191192std::thread *graphicsLoadThread;193194class PrintfLogger : public LogListener {195public:196void Log(const LogMessage &message) override {197// Log with simplified headers as Android already provides timestamp etc.198switch (message.level) {199case LogLevel::LVERBOSE:200case LogLevel::LDEBUG:201case LogLevel::LINFO:202printf("INFO [%s] %s", message.log, message.msg.c_str());203break;204case LogLevel::LERROR:205printf("ERR [%s] %s", message.log, message.msg.c_str());206break;207case LogLevel::LWARNING:208printf("WARN [%s] %s", message.log, message.msg.c_str());209break;210case LogLevel::LNOTICE:211default:212printf("NOTE [%s] !!! %s", message.log, message.msg.c_str());213break;214}215}216};217218// globals219static LogListener *logger = nullptr;220Path boot_filename;221222int NativeMix(short *audio, int numSamples, int sampleRateHz) {223return __AudioMix(audio, numSamples, sampleRateHz);224}225226// This is called before NativeInit so we do a little bit of initialization here.227void NativeGetAppInfo(std::string *app_dir_name, std::string *app_nice_name, bool *landscape, std::string *version) {228*app_nice_name = "PPSSPP";229*app_dir_name = "ppsspp";230*landscape = true;231*version = PPSSPP_GIT_VERSION;232233#if PPSSPP_ARCH(ARM) && defined(__ANDROID__)234ArmEmitterTest();235#elif PPSSPP_ARCH(ARM64) && defined(__ANDROID__)236Arm64EmitterTest();237#endif238}239240#if defined(USING_WIN_UI) && !PPSSPP_PLATFORM(UWP)241static bool CheckFontIsUsable(const wchar_t *fontFace) {242wchar_t actualFontFace[1024] = { 0 };243244HFONT f = CreateFont(0, 0, 0, 0, FW_LIGHT, 0, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, PROOF_QUALITY, VARIABLE_PITCH, fontFace);245if (f != nullptr) {246HDC hdc = CreateCompatibleDC(nullptr);247if (hdc != nullptr) {248SelectObject(hdc, f);249GetTextFace(hdc, 1024, actualFontFace);250DeleteDC(hdc);251}252DeleteObject(f);253}254255// If we were able to get the font name, did it load?256if (actualFontFace[0] != 0) {257return wcsncmp(actualFontFace, fontFace, ARRAY_SIZE(actualFontFace)) == 0;258}259return false;260}261#endif262263void PostLoadConfig() {264if (g_Config.currentDirectory.empty()) {265g_Config.currentDirectory = g_Config.defaultCurrentDirectory;266}267268// Allow the lang directory to be overridden for testing purposes (e.g. Android, where it's hard to269// test new languages without recompiling the entire app, which is a hassle).270const Path langOverridePath = GetSysDirectory(DIRECTORY_SYSTEM) / "lang";271272// If we run into the unlikely case that "lang" is actually a file, just use the built-in translations.273if (!File::Exists(langOverridePath) || !File::IsDirectory(langOverridePath))274g_i18nrepo.LoadIni(g_Config.sLanguageIni);275else276g_i18nrepo.LoadIni(g_Config.sLanguageIni, langOverridePath);277278#if !PPSSPP_PLATFORM(WINDOWS) || PPSSPP_PLATFORM(UWP)279CreateSysDirectories();280#endif281}282283static void CheckFailedGPUBackends() {284#ifdef _DEBUG285// If you're in debug mode, you probably don't want a fallback. If you're in release mode, use IGNORE below.286NOTICE_LOG(Log::Loader, "Not checking for failed graphics backends in debug mode");287return;288#endif289290#if PPSSPP_PLATFORM(ANDROID)291if (System_GetPropertyInt(SYSPROP_SYSTEMVERSION) >= 30) {292// In Android 11 or later, Vulkan is as stable as OpenGL, so let's not even bother.293// Have also seen unexplained issues with random fallbacks to OpenGL for no good reason,294// especially when debugging.295return;296}297#endif298299// We only want to do this once per process run and backend, to detect process crashes.300// If NativeShutdown is called before we finish, we might call this multiple times.301static int lastBackend = -1;302if (lastBackend == g_Config.iGPUBackend) {303return;304}305lastBackend = g_Config.iGPUBackend;306307Path cache = GetSysDirectory(DIRECTORY_APP_CACHE) / "FailedGraphicsBackends.txt";308309if (System_GetPropertyBool(SYSPROP_SUPPORTS_PERMISSIONS)) {310std::string data;311if (File::ReadTextFileToString(cache, &data))312g_Config.sFailedGPUBackends = data;313}314315// Use this if you want to debug a graphics crash...316if (g_Config.sFailedGPUBackends == "IGNORE")317return;318else if (!g_Config.sFailedGPUBackends.empty())319ERROR_LOG(Log::Loader, "Failed graphics backends: %s", g_Config.sFailedGPUBackends.c_str());320321// Okay, let's not try a backend in the failed list.322g_Config.iGPUBackend = g_Config.NextValidBackend();323if (lastBackend != g_Config.iGPUBackend) {324std::string param = GPUBackendToString((GPUBackend)lastBackend) + " -> " + GPUBackendToString((GPUBackend)g_Config.iGPUBackend);325System_GraphicsBackendFailedAlert(param);326WARN_LOG(Log::Loader, "Failed graphics backend switched from %s (%d to %d)", param.c_str(), lastBackend, g_Config.iGPUBackend);327}328// And then let's - for now - add the current to the failed list.329if (g_Config.sFailedGPUBackends.empty()) {330g_Config.sFailedGPUBackends = GPUBackendToString((GPUBackend)g_Config.iGPUBackend);331} else if (g_Config.sFailedGPUBackends.find("ALL") == std::string::npos) {332g_Config.sFailedGPUBackends += "," + GPUBackendToString((GPUBackend)g_Config.iGPUBackend);333}334335if (System_GetPropertyBool(SYSPROP_SUPPORTS_PERMISSIONS)) {336// Let's try to create, in case it doesn't exist.337if (!File::Exists(GetSysDirectory(DIRECTORY_APP_CACHE)))338File::CreateDir(GetSysDirectory(DIRECTORY_APP_CACHE));339File::WriteStringToFile(true, g_Config.sFailedGPUBackends, cache);340} else {341// Just save immediately, since we have storage.342g_Config.Save("got storage permission");343}344}345346static void ClearFailedGPUBackends() {347if (g_Config.sFailedGPUBackends == "IGNORE")348return;349350// We've successfully started graphics without crashing, hurray.351// In case they update drivers and have totally different problems much later, clear the failed list.352g_Config.sFailedGPUBackends.clear();353if (System_GetPropertyBool(SYSPROP_SUPPORTS_PERMISSIONS) || System_GetPropertyBool(SYSPROP_ANDROID_SCOPED_STORAGE)) {354File::Delete(GetSysDirectory(DIRECTORY_APP_CACHE) / "FailedGraphicsBackends.txt");355} else {356g_Config.Save("clearFailedGPUBackends");357}358}359360void NativeInit(int argc, const char *argv[], const char *savegame_dir, const char *external_dir, const char *cache_dir) {361net::Init(); // This needs to happen before we load the config. So on Windows we also run it in Main. It's fine to call multiple times.362363ShaderTranslationInit();364365g_threadManager.Init(cpu_info.num_cores, cpu_info.logical_cpu_count);366367// Make sure UI state is MENU.368ResetUIState();369370bool skipLogo = false;371setlocale( LC_ALL, "C" );372std::string user_data_path = savegame_dir;373pendingMessages.clear();374g_requestManager.Clear();375376// external_dir has all kinds of meanings depending on platform.377// on iOS it's even the path to bundled app assets. It's a mess.378379// We want this to be FIRST.380#if PPSSPP_PLATFORM(IOS) || PPSSPP_PLATFORM(MAC)381// Packed assets are included in app382g_VFS.Register("", new DirectoryReader(Path(external_dir)));383#endif384#if defined(ASSETS_DIR)385g_VFS.Register("", new DirectoryReader(Path(ASSETS_DIR)));386#endif387#if !defined(MOBILE_DEVICE) && !defined(_WIN32) && !PPSSPP_PLATFORM(SWITCH)388g_VFS.Register("", new DirectoryReader(File::GetExeDirectory() / "assets"));389g_VFS.Register("", new DirectoryReader(File::GetExeDirectory()));390g_VFS.Register("", new DirectoryReader(Path("/usr/local/share/ppsspp/assets")));391g_VFS.Register("", new DirectoryReader(Path("/usr/local/share/games/ppsspp/assets")));392g_VFS.Register("", new DirectoryReader(Path("/usr/share/ppsspp/assets")));393g_VFS.Register("", new DirectoryReader(Path("/usr/share/games/ppsspp/assets")));394#endif395396#if PPSSPP_PLATFORM(SWITCH)397Path assetPath = Path(user_data_path) / "assets";398g_VFS.Register("", new DirectoryReader(assetPath));399#else400g_VFS.Register("", new DirectoryReader(Path("assets")));401#endif402g_VFS.Register("", new DirectoryReader(Path(savegame_dir)));403404#if PPSSPP_PLATFORM(WINDOWS) || PPSSPP_PLATFORM(MAC)405g_Config.defaultCurrentDirectory = Path(System_GetProperty(SYSPROP_USER_DOCUMENTS_DIR));406#else407g_Config.defaultCurrentDirectory = Path("/");408#endif409410#if !PPSSPP_PLATFORM(UWP)411g_Config.internalDataDirectory = Path(savegame_dir);412#endif413414#if PPSSPP_PLATFORM(ANDROID)415// In Android 12 with scoped storage, due to the above, the external directory416// is no longer the plain root of external storage, but it's an app specific directory417// on external storage (g_extFilesDir).418if (System_GetPropertyBool(SYSPROP_ANDROID_SCOPED_STORAGE)) {419// There's no sensible default directory. Let the user browse for files.420g_Config.defaultCurrentDirectory.clear();421} else {422g_Config.memStickDirectory = Path(external_dir);423g_Config.defaultCurrentDirectory = Path(external_dir);424}425426// Might also add an option to move it to internal / non-visible storage, but there's427// little point, really.428429g_Config.flash0Directory = Path(external_dir) / "flash0";430431Path memstickDirFile = g_Config.internalDataDirectory / "memstick_dir.txt";432if (File::Exists(memstickDirFile)) {433INFO_LOG(Log::System, "Reading '%s' to find memstick dir.", memstickDirFile.c_str());434std::string memstickDir;435if (File::ReadTextFileToString(memstickDirFile, &memstickDir)) {436Path memstickPath(memstickDir);437if (!memstickPath.empty() && File::Exists(memstickPath)) {438g_Config.memStickDirectory = memstickPath;439INFO_LOG(Log::System, "Memstick Directory from memstick_dir.txt: '%s'", g_Config.memStickDirectory.c_str());440} else {441ERROR_LOG(Log::System, "Couldn't read directory '%s' specified by memstick_dir.txt.", memstickDir.c_str());442if (System_GetPropertyBool(SYSPROP_ANDROID_SCOPED_STORAGE)) {443// Ask the user to configure a memstick directory.444INFO_LOG(Log::System, "Asking the user.");445g_Config.memStickDirectory.clear();446}447}448}449} else {450INFO_LOG(Log::System, "No memstick directory file found (tried to open '%s')", memstickDirFile.c_str());451}452453// Attempt to create directories after reading the path.454if (!System_GetPropertyBool(SYSPROP_ANDROID_SCOPED_STORAGE)) {455CreateSysDirectories();456}457#elif PPSSPP_PLATFORM(UWP) && !defined(__LIBRETRO__)458Path memstickDirFile = g_Config.internalDataDirectory / "memstick_dir.txt";459if (File::Exists(memstickDirFile)) {460INFO_LOG(Log::System, "Reading '%s' to find memstick dir.", memstickDirFile.c_str());461std::string memstickDir;462if (File::ReadTextFileToString(memstickDirFile, &memstickDir)) {463Path memstickPath(memstickDir);464if (!memstickPath.empty() && File::Exists(memstickPath)) {465g_Config.memStickDirectory = memstickPath;466g_Config.SetSearchPath(GetSysDirectory(DIRECTORY_SYSTEM));467g_Config.Reload();468INFO_LOG(Log::System, "Memstick Directory from memstick_dir.txt: '%s'", g_Config.memStickDirectory.c_str());469} else {470ERROR_LOG(Log::System, "Couldn't read directory '%s' specified by memstick_dir.txt.", memstickDir.c_str());471g_Config.memStickDirectory.clear();472}473}474}475else {476INFO_LOG(Log::System, "No memstick directory file found (tried to open '%s')", memstickDirFile.c_str());477}478#elif PPSSPP_PLATFORM(IOS)479g_Config.defaultCurrentDirectory = g_Config.internalDataDirectory;480g_Config.memStickDirectory = DarwinFileSystemServices::appropriateMemoryStickDirectoryToUse();481g_Config.flash0Directory = Path(std::string(external_dir)) / "flash0";482#elif PPSSPP_PLATFORM(MAC)483g_Config.memStickDirectory = DarwinFileSystemServices::appropriateMemoryStickDirectoryToUse();484g_Config.flash0Directory = Path(std::string(external_dir)) / "flash0";485#elif PPSSPP_PLATFORM(SWITCH)486g_Config.memStickDirectory = g_Config.internalDataDirectory / "config/ppsspp";487g_Config.flash0Directory = g_Config.internalDataDirectory / "assets/flash0";488#elif !PPSSPP_PLATFORM(WINDOWS)489std::string config;490if (getenv("XDG_CONFIG_HOME") != NULL)491config = getenv("XDG_CONFIG_HOME");492else if (getenv("HOME") != NULL)493config = getenv("HOME") + std::string("/.config");494else // Just in case495config = "./config";496497g_Config.memStickDirectory = Path(config) / "ppsspp";498g_Config.flash0Directory = File::GetExeDirectory() / "assets/flash0";499if (getenv("HOME") != nullptr) {500g_Config.defaultCurrentDirectory = Path(getenv("HOME"));501} else {502// Hm, should probably actually explicitly set the current directory..503// Though it's not many platforms that'll land us here.504g_Config.currentDirectory = Path(".");505}506#endif507508if (g_Config.currentDirectory.empty()) {509g_Config.currentDirectory = g_Config.defaultCurrentDirectory;510}511512if (cache_dir && strlen(cache_dir)) {513g_Config.appCacheDirectory = Path(cache_dir);514DiskCachingFileLoaderCache::SetCacheDir(g_Config.appCacheDirectory);515}516517if (!LogManager::GetInstance()) {518LogManager::Init(&g_Config.bEnableLogging);519}520521#if !PPSSPP_PLATFORM(WINDOWS)522g_Config.SetSearchPath(GetSysDirectory(DIRECTORY_SYSTEM));523524// Note that if we don't have storage permission here, loading the config will525// fail and it will be set to the default. Later, we load again when we get permission.526g_Config.Load();527#endif528529LogManager *logman = LogManager::GetInstance();530531const char *fileToLog = 0;532Path stateToLoad;533534bool gotBootFilename = false;535bool gotoGameSettings = false;536bool gotoTouchScreenTest = false;537bool gotoDeveloperTools = false;538boot_filename.clear();539540// Parse command line541LogLevel logLevel = LogLevel::LINFO;542bool forceLogLevel = false;543const auto setLogLevel = [&logLevel, &forceLogLevel](LogLevel level) {544logLevel = level;545forceLogLevel = true;546};547548for (int i = 1; i < argc; i++) {549if (argv[i][0] == '-') {550#if defined(__APPLE__)551// On Apple system debugged executable may get -NSDocumentRevisionsDebugMode YES in argv.552if (!strcmp(argv[i], "-NSDocumentRevisionsDebugMode") && argc - 1 > i) {553i++;554continue;555}556#endif557switch (argv[i][1]) {558case 'd':559// Enable debug logging560// Note that you must also change the max log level in Log.h.561setLogLevel(LogLevel::LDEBUG);562break;563case 'v':564// Enable verbose logging565// Note that you must also change the max log level in Log.h.566setLogLevel(LogLevel::LVERBOSE);567break;568case 'j':569g_Config.iCpuCore = (int)CPUCore::JIT;570g_Config.bSaveSettings = false;571break;572case 'i':573g_Config.iCpuCore = (int)CPUCore::INTERPRETER;574g_Config.bSaveSettings = false;575break;576case 'r':577g_Config.iCpuCore = (int)CPUCore::IR_INTERPRETER;578g_Config.bSaveSettings = false;579break;580case 'J':581g_Config.iCpuCore = (int)CPUCore::JIT_IR;582g_Config.bSaveSettings = false;583break;584case '-':585if (!strncmp(argv[i], "--loglevel=", strlen("--loglevel=")) && strlen(argv[i]) > strlen("--loglevel="))586setLogLevel(static_cast<LogLevel>(std::atoi(argv[i] + strlen("--loglevel="))));587if (!strncmp(argv[i], "--log=", strlen("--log=")) && strlen(argv[i]) > strlen("--log="))588fileToLog = argv[i] + strlen("--log=");589if (!strncmp(argv[i], "--state=", strlen("--state=")) && strlen(argv[i]) > strlen("--state="))590stateToLoad = Path(std::string(argv[i] + strlen("--state=")));591if (!strncmp(argv[i], "--escape-exit", strlen("--escape-exit")))592g_Config.bPauseExitsEmulator = true;593if (!strncmp(argv[i], "--pause-menu-exit", strlen("--pause-menu-exit")))594g_Config.bPauseMenuExitsEmulator = true;595if (!strcmp(argv[i], "--fullscreen")) {596g_Config.iForceFullScreen = 1;597System_ToggleFullscreenState("1");598}599if (!strcmp(argv[i], "--windowed")) {600g_Config.iForceFullScreen = 0;601System_ToggleFullscreenState("0");602}603if (!strcmp(argv[i], "--touchscreentest"))604gotoTouchScreenTest = true;605if (!strcmp(argv[i], "--gamesettings"))606gotoGameSettings = true;607if (!strcmp(argv[i], "--developertools"))608gotoDeveloperTools = true;609if (!strncmp(argv[i], "--appendconfig=", strlen("--appendconfig=")) && strlen(argv[i]) > strlen("--appendconfig=")) {610g_Config.SetAppendedConfigIni(Path(std::string(argv[i] + strlen("--appendconfig="))));611g_Config.LoadAppendedConfig();612}613break;614}615} else {616// This parameter should be a boot filename. Only accept it if we617// don't already have one.618if (!gotBootFilename) {619gotBootFilename = true;620INFO_LOG(Log::System, "Boot filename found in args: '%s'", argv[i]);621622bool okToLoad = true;623bool okToCheck = true;624if (System_GetPropertyBool(SYSPROP_SUPPORTS_PERMISSIONS)) {625PermissionStatus status = System_GetPermissionStatus(SYSTEM_PERMISSION_STORAGE);626if (status == PERMISSION_STATUS_DENIED) {627ERROR_LOG(Log::IO, "Storage permission denied. Launching without argument.");628okToLoad = false;629okToCheck = false;630} else if (status != PERMISSION_STATUS_GRANTED) {631ERROR_LOG(Log::IO, "Storage permission not granted. Launching without argument check.");632okToCheck = false;633} else {634INFO_LOG(Log::IO, "Storage permission granted.");635}636}637if (okToLoad) {638std::string str = std::string(argv[i]);639// Handle file:/// URIs, since you get those when creating shortcuts on some Android systems.640if (startsWith(str, "file:///")) {641str = UriDecode(str.substr(7));642INFO_LOG(Log::IO, "Decoding '%s' to '%s'", argv[i], str.c_str());643}644645boot_filename = Path(str);646skipLogo = true;647}648if (okToLoad && okToCheck) {649std::unique_ptr<FileLoader> fileLoader(ConstructFileLoader(boot_filename));650if (!fileLoader->Exists()) {651fprintf(stderr, "File not found: %s\n", boot_filename.c_str());652#if defined(_WIN32) || defined(__ANDROID__)653// Ignore and proceed.654#else655// Bail.656exit(1);657#endif658}659}660} else {661fprintf(stderr, "Syntax error: Can only boot one file.\nNote: Many command line args need a =, like --appendconfig=FILENAME.ini.\n");662#if defined(_WIN32) || defined(__ANDROID__)663// Ignore and proceed.664#else665// Bail.666exit(1);667#endif668}669}670}671672if (fileToLog)673LogManager::GetInstance()->ChangeFileLog(fileToLog);674675if (forceLogLevel)676LogManager::GetInstance()->SetAllLogLevels(logLevel);677678PostLoadConfig();679680#if PPSSPP_PLATFORM(ANDROID)681logger = new AndroidLogger();682logman->AddListener(logger);683#elif (defined(MOBILE_DEVICE) && !defined(_DEBUG))684// Enable basic logging for any kind of mobile device, since LogManager doesn't.685// The MOBILE_DEVICE/_DEBUG condition matches LogManager.cpp.686logger = new PrintfLogger();687logman->AddListener(logger);688#endif689690if (System_GetPropertyBool(SYSPROP_SUPPORTS_PERMISSIONS)) {691if (System_GetPermissionStatus(SYSTEM_PERMISSION_STORAGE) != PERMISSION_STATUS_GRANTED) {692System_AskForPermission(SYSTEM_PERMISSION_STORAGE);693}694}695696auto des = GetI18NCategory(I18NCat::DESKTOPUI);697// Note to translators: do not translate this/add this to PPSSPP-lang's files.698// It's intended to be custom for every user.699// Only add it to your own personal copies of PPSSPP.700#if PPSSPP_PLATFORM(UWP)701// Roboto font is loaded in TextDrawerUWP.702g_Config.sFont = des->T("Font", "Roboto");703#elif defined(USING_WIN_UI) && !PPSSPP_PLATFORM(UWP)704// TODO: Could allow a setting to specify a font file to load?705// TODO: Make this a constant if we can sanely load the font on other systems?706AddFontResourceEx(L"assets/Roboto-Condensed.ttf", FR_PRIVATE, NULL);707// The font goes by two names, let's allow either one.708if (CheckFontIsUsable(L"Roboto Condensed")) {709g_Config.sFont = des->T("Font", "Roboto Condensed");710} else {711g_Config.sFont = des->T("Font", "Roboto");712}713#elif defined(USING_QT_UI)714size_t fontSize = 0;715uint8_t *fontData = g_VFS.ReadFile("Roboto-Condensed.ttf", &fontSize);716if (fontData) {717int fontID = QFontDatabase::addApplicationFontFromData(QByteArray((const char *)fontData, fontSize));718delete [] fontData;719720QStringList fontsFound = QFontDatabase::applicationFontFamilies(fontID);721if (fontsFound.size() >= 1) {722// Might be "Roboto" or "Roboto Condensed".723g_Config.sFont = des->T("Font", fontsFound.at(0).toUtf8().constData());724}725} else {726// Let's try for it being a system font.727g_Config.sFont = des->T("Font", "Roboto Condensed");728}729#endif730731// TODO: Load these in the background instead of synchronously.732g_BackgroundAudio.SFX().LoadSamples();733734if (!boot_filename.empty() && stateToLoad.Valid()) {735SaveState::Load(stateToLoad, -1, [](SaveState::Status status, std::string_view message, void *) {736if (!message.empty() && (!g_Config.bDumpFrames || !g_Config.bDumpVideoOutput)) {737g_OSD.Show(status == SaveState::Status::SUCCESS ? OSDType::MESSAGE_SUCCESS : OSDType::MESSAGE_ERROR,738message, status == SaveState::Status::SUCCESS ? 2.0 : 5.0);739}740});741}742743if (g_Config.bAchievementsEnable) {744FILE *iconCacheFile = File::OpenCFile(GetSysDirectory(DIRECTORY_CACHE) / "icon.cache", "rb");745if (iconCacheFile) {746g_iconCache.LoadFromFile(iconCacheFile);747fclose(iconCacheFile);748}749}750751DEBUG_LOG(Log::System, "ScreenManager!");752g_screenManager = new ScreenManager();753if (g_Config.memStickDirectory.empty()) {754INFO_LOG(Log::System, "No memstick directory! Asking for one to be configured.");755g_screenManager->switchScreen(new LogoScreen(AfterLogoScreen::MEMSTICK_SCREEN_INITIAL_SETUP));756} else if (gotoGameSettings) {757g_screenManager->switchScreen(new LogoScreen(AfterLogoScreen::TO_GAME_SETTINGS));758} else if (gotoTouchScreenTest) {759g_screenManager->switchScreen(new MainScreen());760g_screenManager->push(new TouchTestScreen(Path()));761} else if (gotoDeveloperTools) {762g_screenManager->switchScreen(new MainScreen());763g_screenManager->push(new DeveloperToolsScreen(Path()));764} else if (skipLogo) {765g_screenManager->switchScreen(new EmuScreen(boot_filename));766} else {767g_screenManager->switchScreen(new LogoScreen(AfterLogoScreen::DEFAULT));768}769770g_screenManager->SetBackgroundOverlayScreens(new BackgroundScreen(), new OSDOverlayScreen());771772// Easy testing773// screenManager->push(new GPUDriverTestScreen());774775if (g_Config.bRemoteShareOnStartup && g_Config.bRemoteDebuggerOnStartup)776StartWebServer(WebServerFlags::ALL);777else if (g_Config.bRemoteShareOnStartup)778StartWebServer(WebServerFlags::DISCS);779else if (g_Config.bRemoteDebuggerOnStartup)780StartWebServer(WebServerFlags::DEBUGGER);781782std::string sysName = System_GetProperty(SYSPROP_NAME);783isOuya = KeyMap::IsOuya(sysName);784785// We do this here, instead of in NativeInitGraphics, because the display may be reset.786// When it's reset we don't want to forget all our managed things.787CheckFailedGPUBackends();788SetGPUBackend((GPUBackend)g_Config.iGPUBackend);789renderCounter = 0;790791// Initialize retro achievements runtime.792Achievements::Initialize();793794// Must be done restarting by now.795restarting = false;796}797798void CallbackPostRender(UIContext *dc, void *userdata);799bool CreateGlobalPipelines();800801bool NativeInitGraphics(GraphicsContext *graphicsContext) {802INFO_LOG(Log::System, "NativeInitGraphics");803804_assert_msg_(g_screenManager, "No screenmanager, bad init order. Backend = %d", g_Config.iGPUBackend);805806// We set this now so any resize during init is processed later.807resized = false;808809Core_SetGraphicsContext(graphicsContext);810g_draw = graphicsContext->GetDrawContext();811812_assert_(g_draw);813814if (!CreateGlobalPipelines()) {815ERROR_LOG(Log::G3D, "Failed to create global pipelines");816return false;817}818819ui_draw2d.SetAtlas(GetUIAtlas());820ui_draw2d.SetFontAtlas(GetFontAtlas());821822uiContext = new UIContext();823uiContext->theme = GetTheme();824UpdateTheme(uiContext);825826ui_draw2d.Init(g_draw, texColorPipeline);827828uiContext->Init(g_draw, texColorPipeline, colorPipeline, &ui_draw2d);829if (uiContext->Text())830uiContext->Text()->SetFont("Tahoma", 20, 0);831832g_screenManager->setUIContext(uiContext);833g_screenManager->setDrawContext(g_draw);834g_screenManager->setPostRenderCallback(&CallbackPostRender, nullptr);835g_screenManager->deviceRestored();836837#ifdef _WIN32838winAudioBackend = CreateAudioBackend((AudioBackendType)g_Config.iAudioBackend);839#if PPSSPP_PLATFORM(UWP)840winAudioBackend->Init(0, &NativeMix, 44100);841#else842winAudioBackend->Init(MainWindow::GetHWND(), &NativeMix, 44100);843#endif844#endif845846#if defined(_WIN32) && !PPSSPP_PLATFORM(UWP)847if (IsWin7OrHigher()) {848winCamera = new WindowsCaptureDevice(CAPTUREDEVIDE_TYPE::VIDEO);849winCamera->sendMessage({ CAPTUREDEVIDE_COMMAND::INITIALIZE, nullptr });850winMic = new WindowsCaptureDevice(CAPTUREDEVIDE_TYPE::Audio);851winMic->sendMessage({ CAPTUREDEVIDE_COMMAND::INITIALIZE, nullptr });852}853#endif854855g_gameInfoCache = new GameInfoCache();856857if (gpu) {858PSP_CoreParameter().pixelWidth = g_display.pixel_xres;859PSP_CoreParameter().pixelHeight = g_display.pixel_yres;860gpu->DeviceRestore(g_draw);861}862863INFO_LOG(Log::System, "NativeInitGraphics completed");864865return true;866}867868bool CreateGlobalPipelines() {869using namespace Draw;870871ShaderModule *vs_color_2d = g_draw->GetVshaderPreset(VS_COLOR_2D);872ShaderModule *fs_color_2d = g_draw->GetFshaderPreset(FS_COLOR_2D);873ShaderModule *vs_texture_color_2d = g_draw->GetVshaderPreset(VS_TEXTURE_COLOR_2D);874ShaderModule *fs_texture_color_2d = g_draw->GetFshaderPreset(FS_TEXTURE_COLOR_2D);875876if (!vs_color_2d || !fs_color_2d || !vs_texture_color_2d || !fs_texture_color_2d) {877ERROR_LOG(Log::G3D, "Failed to get shader preset");878return false;879}880881InputLayout *inputLayout = ui_draw2d.CreateInputLayout(g_draw);882BlendState *blendNormal = g_draw->CreateBlendState({ true, 0xF, BlendFactor::SRC_ALPHA, BlendFactor::ONE_MINUS_SRC_ALPHA });883DepthStencilState *depth = g_draw->CreateDepthStencilState({ false, false, Comparison::LESS });884RasterState *rasterNoCull = g_draw->CreateRasterState({});885886PipelineDesc colorDesc{887Primitive::TRIANGLE_LIST,888{ vs_color_2d, fs_color_2d },889inputLayout, depth, blendNormal, rasterNoCull, &vsColBufDesc,890};891PipelineDesc texColorDesc{892Primitive::TRIANGLE_LIST,893{ vs_texture_color_2d, fs_texture_color_2d },894inputLayout, depth, blendNormal, rasterNoCull, &vsTexColBufDesc,895};896897colorPipeline = g_draw->CreateGraphicsPipeline(colorDesc, "global_color");898if (!colorPipeline) {899// Something really critical is wrong, don't care much about correct releasing of the states.900return false;901}902903texColorPipeline = g_draw->CreateGraphicsPipeline(texColorDesc, "global_texcolor");904if (!texColorPipeline) {905// Something really critical is wrong, don't care much about correct releasing of the states.906return false;907}908909// Release these now, reference counting should ensure that they get completely released910// once we delete both pipelines.911inputLayout->Release();912rasterNoCull->Release();913blendNormal->Release();914depth->Release();915return true;916}917918void NativeShutdownGraphics() {919INFO_LOG(Log::System, "NativeShutdownGraphics");920921if (g_screenManager) {922g_screenManager->deviceLost();923}924g_iconCache.ClearTextures();925926// TODO: This is not really necessary with Vulkan on Android - could keep shaders etc in memory927if (gpu)928gpu->DeviceLost();929930#if PPSSPP_PLATFORM(WINDOWS)931delete winAudioBackend;932winAudioBackend = nullptr;933#endif934935#if PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP)936if (winCamera) {937winCamera->waitShutDown();938delete winCamera;939winCamera = nullptr;940}941if (winMic) {942winMic->waitShutDown();943delete winMic;944winMic = nullptr;945}946#endif947948UIBackgroundShutdown();949950delete g_gameInfoCache;951g_gameInfoCache = nullptr;952953delete uiContext;954uiContext = nullptr;955956ui_draw2d.Shutdown();957958if (colorPipeline) {959colorPipeline->Release();960colorPipeline = nullptr;961}962if (texColorPipeline) {963texColorPipeline->Release();964texColorPipeline = nullptr;965}966967INFO_LOG(Log::System, "NativeShutdownGraphics done");968}969970static void TakeScreenshot(Draw::DrawContext *draw) {971Path path = GetSysDirectory(DIRECTORY_SCREENSHOT);972if (!File::Exists(path)) {973File::CreateDir(path);974}975976// First, find a free filename.977int i = 0;978979std::string gameId = g_paramSFO.GetDiscID();980981Path filename;982while (i < 10000){983if (g_Config.bScreenshotsAsPNG)984filename = path / StringFromFormat("%s_%05d.png", gameId.c_str(), i);985else986filename = path / StringFromFormat("%s_%05d.jpg", gameId.c_str(), i);987File::FileInfo info;988if (!File::Exists(filename))989break;990i++;991}992993bool success = TakeGameScreenshot(draw, filename, g_Config.bScreenshotsAsPNG ? ScreenshotFormat::PNG : ScreenshotFormat::JPG, SCREENSHOT_OUTPUT);994if (success) {995g_OSD.Show(OSDType::MESSAGE_FILE_LINK, filename.ToString(), 0.0f, "screenshot_link");996if (System_GetPropertyBool(SYSPROP_CAN_SHOW_FILE)) {997g_OSD.SetClickCallback("screenshot_link", [](bool clicked, void *data) -> void {998Path *path = reinterpret_cast<Path *>(data);999if (clicked) {1000System_ShowFileInFolder(*path);1001} else {1002delete path;1003}1004}, new Path(filename));1005}1006} else {1007auto err = GetI18NCategory(I18NCat::ERRORS);1008g_OSD.Show(OSDType::MESSAGE_ERROR, err->T("Could not save screenshot file"));1009}1010}10111012void CallbackPostRender(UIContext *dc, void *userdata) {1013if (g_TakeScreenshot) {1014TakeScreenshot(dc->GetDrawContext());1015g_TakeScreenshot = false;1016}1017}10181019static Matrix4x4 ComputeOrthoMatrix(float xres, float yres) {1020// TODO: Should be able to share the y-flip logic here with the one in postprocessing/presentation, for example.1021Matrix4x4 ortho;1022switch (GetGPUBackend()) {1023case GPUBackend::VULKAN:1024ortho.setOrthoD3D(0.0f, xres, 0, yres, -1.0f, 1.0f);1025break;1026case GPUBackend::DIRECT3D9:1027ortho.setOrthoD3D(0.0f, xres, yres, 0.0f, -1.0f, 1.0f);1028Matrix4x4 translation;1029// Account for the small window adjustment.1030translation.setTranslation(Vec3(1031-0.5f * g_display.dpi_scale_x / g_display.dpi_scale_real_x,1032-0.5f * g_display.dpi_scale_y / g_display.dpi_scale_real_y, 0.0f));1033ortho = translation * ortho;1034break;1035case GPUBackend::DIRECT3D11:1036ortho.setOrthoD3D(0.0f, xres, yres, 0.0f, -1.0f, 1.0f);1037break;1038case GPUBackend::OPENGL:1039default:1040ortho.setOrtho(0.0f, xres, yres, 0.0f, -1.0f, 1.0f);1041break;1042}10431044// Compensate for rotated display if needed.1045if (g_display.rotation != DisplayRotation::ROTATE_0) {1046ortho = ortho * g_display.rot_matrix;1047}1048return ortho;1049}10501051static void SendMouseDeltaAxis();10521053void NativeFrame(GraphicsContext *graphicsContext) {1054PROFILE_END_FRAME();10551056// This can only be accessed from Windows currently, and causes linking errors with headless etc.1057if (g_restartGraphics == 1) {1058// Used for debugging only.1059NativeShutdownGraphics();1060g_restartGraphics++;1061return;1062}1063else if (g_restartGraphics == 2) {1064NativeInitGraphics(graphicsContext);1065g_restartGraphics = 0;1066}10671068double startTime = time_now_d();10691070ProcessWheelRelease(NKCODE_EXT_MOUSEWHEEL_UP, startTime, false);1071ProcessWheelRelease(NKCODE_EXT_MOUSEWHEEL_DOWN, startTime, false);10721073// it's ok to call this redundantly with DoFrame from EmuScreen1074Achievements::Idle();10751076g_DownloadManager.Update();10771078g_Discord.Update();10791080g_OSD.Update();10811082UI::SetSoundEnabled(g_Config.bUISound);10831084_dbg_assert_(graphicsContext != nullptr);1085_dbg_assert_(g_screenManager != nullptr);10861087g_GameManager.Update();10881089if (GetUIState() != UISTATE_INGAME) {1090// Note: We do this from NativeFrame so that the graphics context is1091// guaranteed valid, to be safe - g_gameInfoCache messes around with textures.1092g_BackgroundAudio.Update();1093}10941095g_iconCache.FrameUpdate();10961097g_screenManager->update();10981099// Do this after g_screenManager.update() so we can receive setting changes before rendering.1100std::vector<PendingMessage> toProcess;1101{1102std::lock_guard<std::mutex> lock(pendingMutex);1103toProcess = std::move(pendingMessages);1104pendingMessages.clear();1105}11061107for (const auto &item : toProcess) {1108if (HandleGlobalMessage(item.message, item.value)) {1109// TODO: Add a to-string thingy.1110INFO_LOG(Log::System, "Handled global message: %d / %s", (int)item.message, item.value.c_str());1111}1112g_screenManager->sendMessage(item.message, item.value.c_str());1113}11141115g_requestManager.ProcessRequests();11161117// Apply the UIContext bounds as a 2D transformation matrix.1118// TODO: This should be moved into the draw context...1119Matrix4x4 ortho = ComputeOrthoMatrix(g_display.dp_xres, g_display.dp_yres);11201121Draw::DebugFlags debugFlags = Draw::DebugFlags::NONE;1122if ((DebugOverlay)g_Config.iDebugOverlay == DebugOverlay::GPU_PROFILE)1123debugFlags |= Draw::DebugFlags::PROFILE_TIMESTAMPS;1124if (g_Config.bGpuLogProfiler)1125debugFlags |= Draw::DebugFlags::PROFILE_SCOPES;11261127g_frameTiming.Reset(g_draw);11281129g_draw->BeginFrame(debugFlags);11301131ui_draw2d.PushDrawMatrix(ortho);11321133g_screenManager->getUIContext()->SetTintSaturation(g_Config.fUITint, g_Config.fUISaturation);11341135// All actual rendering happen in here.1136ScreenRenderFlags renderFlags = g_screenManager->render();1137if (g_screenManager->getUIContext()->Text()) {1138g_screenManager->getUIContext()->Text()->OncePerFrame();1139}11401141ui_draw2d.PopDrawMatrix();11421143g_draw->EndFrame();11441145// This, between EndFrame and Present, is where we should actually wait to do present time management.1146// There might not be a meaningful distinction here for all backends..1147g_frameTiming.PostSubmit();11481149if (renderCounter < 10 && ++renderCounter == 10) {1150// We're rendering fine, clear out failure info.1151ClearFailedGPUBackends();1152}11531154int interval;1155Draw::PresentMode presentMode = ComputePresentMode(g_draw, &interval);1156g_draw->Present(presentMode, interval);11571158if (resized) {1159INFO_LOG(Log::G3D, "Resized flag set - recalculating bounds");1160resized = false;11611162if (uiContext) {1163// Modifying the bounds here can be used to "inset" the whole image to gain borders for TV overscan etc.1164// The UI now supports any offset but not the EmuScreen yet.1165uiContext->SetBounds(Bounds(0, 0, g_display.dp_xres, g_display.dp_yres));11661167// OSX 10.6 and SDL 1.2 bug.1168#if defined(__APPLE__) && !defined(USING_QT_UI)1169static int dp_xres_old = g_display.dp_xres;1170if (g_display.dp_xres != dp_xres_old) {1171dp_xres_old = g_display.dp_xres;1172}1173#endif1174}11751176graphicsContext->Resize();1177g_screenManager->resized();11781179// TODO: Move this to the GraphicsContext objects for each backend.1180#if !PPSSPP_PLATFORM(WINDOWS) && !defined(ANDROID)1181PSP_CoreParameter().pixelWidth = g_display.pixel_xres;1182PSP_CoreParameter().pixelHeight = g_display.pixel_yres;1183System_PostUIMessage(UIMessage::GPU_DISPLAY_RESIZED);1184#endif1185} else {1186// INFO_LOG(Log::G3D, "Polling graphics context");1187graphicsContext->Poll();1188}11891190SendMouseDeltaAxis();11911192if (!(renderFlags & ScreenRenderFlags::HANDLED_THROTTLING)) {1193// TODO: We should ideally mix this with game audio.1194g_BackgroundAudio.Play();11951196float refreshRate = System_GetPropertyFloat(SYSPROP_DISPLAY_REFRESH_RATE);1197// Simple throttling to not burn the GPU in the menu.1198// TODO: This should move into NativeFrame. Also, it's only necessary in MAILBOX or IMMEDIATE presentation modes.1199double diffTime = time_now_d() - startTime;1200int sleepTime = (int)(1000.0 / refreshRate) - (int)(diffTime * 1000.0);1201if (sleepTime > 0)1202sleep_ms(sleepTime);1203}1204}12051206bool HandleGlobalMessage(UIMessage message, const std::string &value) {1207if (message == UIMessage::RESTART_GRAPHICS) {1208g_restartGraphics = 1;1209return true;1210} else if (message == UIMessage::SAVESTATE_DISPLAY_SLOT) {1211auto sy = GetI18NCategory(I18NCat::SYSTEM);1212std::string msg = StringFromFormat("%s: %d", std::string(sy->T("Savestate Slot")).c_str(), SaveState::GetCurrentSlot() + 1);1213// Show for the same duration as the preview.1214g_OSD.Show(OSDType::MESSAGE_INFO, msg, 2.0f, "savestate_slot");1215return true;1216}1217else if (message == UIMessage::GPU_DISPLAY_RESIZED) {1218if (gpu) {1219gpu->NotifyDisplayResized();1220}1221return true;1222}1223else if (message == UIMessage::GPU_RENDER_RESIZED) {1224if (gpu) {1225gpu->NotifyRenderResized();1226}1227return true;1228}1229else if (message == UIMessage::GPU_CONFIG_CHANGED) {1230if (gpu) {1231gpu->NotifyConfigChanged();1232}1233Reporting::UpdateConfig();1234return true;1235}1236else if (message == UIMessage::POWER_SAVING) {1237if (value != "false") {1238auto sy = GetI18NCategory(I18NCat::SYSTEM);1239#if PPSSPP_PLATFORM(ANDROID)1240g_OSD.Show(OSDType::MESSAGE_WARNING, sy->T("WARNING: Android battery save mode is on"), 2.0f, "core_powerSaving");1241#else1242g_OSD.Show(OSDType::MESSAGE_WARNING, sy->T("WARNING: Battery save mode is on"), 2.0f, "core_powerSaving");1243#endif1244}1245Core_SetPowerSaving(value != "false");1246return true;1247}1248else if (message == UIMessage::PERMISSION_GRANTED && value == "storage") {1249CreateSysDirectories();1250// We must have failed to load the config before, so load it now to avoid overwriting the old config1251// with a freshly generated one.1252// NOTE: If graphics backend isn't what's in the config (due to error fallback, or not matching the default1253// and then getting permission), it will get out of sync. So we save and restore g_Config.iGPUBackend.1254// Ideally we should simply reinitialize graphics to the mode from the config, but there are potential issues.1255int gpuBackend = g_Config.iGPUBackend;1256INFO_LOG(Log::IO, "Reloading config after storage permission grant.");1257g_Config.Reload();1258PostLoadConfig();1259g_Config.iGPUBackend = gpuBackend;1260return true;1261} else if (message == UIMessage::APP_RESUMED || message == UIMessage::GOT_FOCUS) {1262// Assume that the user may have modified things.1263MemoryStick_NotifyWrite();1264return true;1265} else {1266return false;1267}1268}12691270bool NativeIsAtTopLevel() {1271// This might need some synchronization?1272if (!g_screenManager) {1273ERROR_LOG(Log::System, "No screen manager active");1274return false;1275}1276Screen *currentScreen = g_screenManager->topScreen();1277if (currentScreen) {1278bool top = currentScreen->isTopLevel();1279INFO_LOG(Log::System, "Screen toplevel: %i", (int)top);1280return currentScreen->isTopLevel();1281} else {1282ERROR_LOG(Log::System, "No current screen");1283return false;1284}1285}12861287void NativeTouch(const TouchInput &touch) {1288if (!g_screenManager) {1289return;1290}12911292// Brute force prevent NaNs from getting into the UI system.1293// Don't think this is actually necessary in practice.1294if (my_isnan(touch.x) || my_isnan(touch.y)) {1295return;1296}1297g_screenManager->touch(touch);1298}12991300// up, down1301static double g_wheelReleaseTime[2]{};13021303static void ProcessWheelRelease(InputKeyCode keyCode, double now, bool keyPress) {1304int dir = keyCode - NKCODE_EXT_MOUSEWHEEL_UP;1305if (g_wheelReleaseTime[dir] != 0.0 && (keyPress || now >= g_wheelReleaseTime[dir])) {1306g_wheelReleaseTime[dir] = 0.0;1307KeyInput key{};1308key.deviceId = DEVICE_ID_MOUSE;1309key.keyCode = keyCode;1310key.flags = KEY_UP;1311NativeKey(key);1312}13131314if (keyPress) {1315float releaseTime = (float)g_Config.iMouseWheelUpDelayMs * (1.0f / 1000.0f);1316g_wheelReleaseTime[dir] = now + releaseTime;1317}1318}13191320bool NativeKey(const KeyInput &key) {1321double now = time_now_d();13221323// VR actions1324if ((IsVREnabled() || g_Config.bForceVR) && !UpdateVRKeys(key)) {1325return false;1326}13271328#if PPSSPP_PLATFORM(UWP)1329// Ignore if key sent from OnKeyDown/OnKeyUp/XInput while text edit active1330// it's already handled by `OnCharacterReceived`1331if (IgnoreInput(key.keyCode) && !(key.flags & KEY_CHAR)) {1332return false;1333}1334#endif13351336// INFO_LOG(Log::System, "Key code: %i flags: %i", key.keyCode, key.flags);1337#if !defined(MOBILE_DEVICE)1338if (g_Config.bPauseExitsEmulator) {1339std::vector<int> pspKeys;1340pspKeys.clear();1341if (KeyMap::InputMappingToPspButton(InputMapping(key.deviceId, key.keyCode), &pspKeys)) {1342if (std::find(pspKeys.begin(), pspKeys.end(), VIRTKEY_PAUSE) != pspKeys.end()) {1343System_ExitApp();1344return true;1345}1346}1347}1348#endif13491350if (!g_screenManager) {1351return false;1352}13531354// Handle releases of mousewheel keys.1355if ((key.flags & KEY_DOWN) && key.deviceId == DEVICE_ID_MOUSE && (key.keyCode == NKCODE_EXT_MOUSEWHEEL_UP || key.keyCode == NKCODE_EXT_MOUSEWHEEL_DOWN)) {1356ProcessWheelRelease(key.keyCode, now, true);1357}13581359HLEPlugins::SetKey(key.keyCode, (key.flags & KEY_DOWN) ? 1 : 0);1360// Dispatch the key event.1361bool retval = g_screenManager->key(key);13621363// The Mode key can have weird consequences on some devices, see #17245.1364if (key.keyCode == NKCODE_BUTTON_MODE) {1365// Tell the caller that we handled the key.1366retval = true;1367}13681369return retval;1370}13711372void NativeAxis(const AxisInput *axes, size_t count) {1373// VR actions1374if ((IsVREnabled() || g_Config.bForceVR) && !UpdateVRAxis(axes, count)) {1375return;1376}13771378if (!g_screenManager) {1379// Too early.1380return;1381}13821383g_screenManager->axis(axes, count);13841385for (size_t i = 0; i < count; i++) {1386const AxisInput &axis = axes[i];1387HLEPlugins::PluginDataAxis[axis.axisId] = axis.value;1388}1389}13901391// Called from NativeFrame and from NativeMouseDelta.1392static void SendMouseDeltaAxis() {1393float mx, my;1394MouseEventProcessor::MouseDeltaToAxes(time_now_d(), &mx, &my);13951396AxisInput axis[2];1397axis[0].axisId = JOYSTICK_AXIS_MOUSE_REL_X;1398axis[0].deviceId = DEVICE_ID_MOUSE;1399axis[0].value = mx;1400axis[1].axisId = JOYSTICK_AXIS_MOUSE_REL_Y;1401axis[1].deviceId = DEVICE_ID_MOUSE;1402axis[1].value = my;14031404HLEPlugins::PluginDataAxis[JOYSTICK_AXIS_MOUSE_REL_X] = mx;1405HLEPlugins::PluginDataAxis[JOYSTICK_AXIS_MOUSE_REL_Y] = my;14061407//NOTICE_LOG(Log::System, "delta: %0.2f %0.2f mx/my: %0.2f %0.2f dpi: %f sens: %f ",1408// g_mouseDeltaX, g_mouseDeltaY, mx, my, g_display.dpi_scale_x, g_Config.fMouseSensitivity);14091410if (GetUIState() == UISTATE_INGAME || g_Config.bMapMouse) {1411NativeAxis(axis, 2);1412}1413}14141415void NativeMouseDelta(float dx, float dy) {1416// Remap, shared code. Then send it as a regular axis event.1417if (!g_Config.bMouseControl)1418return;14191420MouseEventProcessor::ProcessDelta(time_now_d(), dx, dy);14211422SendMouseDeltaAxis();1423}14241425void NativeAccelerometer(float tiltX, float tiltY, float tiltZ) {1426if (g_Config.iTiltInputType == TILT_NULL) {1427// if tilt events are disabled, don't do anything special.1428return;1429}14301431// create the base coordinate tilt system from the calibration data.1432float tiltBaseAngleY = g_Config.fTiltBaseAngleY;14331434// Figure out the sensitivity of the tilt. (sensitivity is originally 0 - 100)1435// We divide by 50, so that the rest of the 50 units can be used to overshoot the1436// target. If you want precise control, you'd keep the sensitivity ~50.1437// For games that don't need much control but need fast reactions,1438// then a value of 70-80 is the way to go.1439float xSensitivity = g_Config.iTiltSensitivityX / 50.0;1440float ySensitivity = g_Config.iTiltSensitivityY / 50.0;14411442// x and y are flipped if we are in landscape orientation. The events are1443// sent with respect to the portrait coordinate system, while we1444// take all events in landscape.1445// see [http://developer.android.com/guide/topics/sensors/sensors_overview.html] for details1446bool landscape = g_display.dp_yres < g_display.dp_xres;1447// now transform out current tilt to the calibrated coordinate system1448TiltEventProcessor::ProcessTilt(landscape, tiltBaseAngleY, tiltX, tiltY, tiltZ,1449g_Config.bInvertTiltX, g_Config.bInvertTiltY,1450xSensitivity, ySensitivity);14511452HLEPlugins::PluginDataAxis[JOYSTICK_AXIS_ACCELEROMETER_X] = tiltX;1453HLEPlugins::PluginDataAxis[JOYSTICK_AXIS_ACCELEROMETER_Y] = tiltY;1454HLEPlugins::PluginDataAxis[JOYSTICK_AXIS_ACCELEROMETER_Z] = tiltZ;1455}14561457void System_PostUIMessage(UIMessage message, const std::string &value) {1458std::lock_guard<std::mutex> lock(pendingMutex);1459PendingMessage pendingMessage;1460pendingMessage.message = message;1461pendingMessage.value = value;1462pendingMessages.push_back(pendingMessage);1463}14641465void NativeResized() {1466// NativeResized can come from any thread so we just set a flag, then process it later.1467VERBOSE_LOG(Log::G3D, "NativeResized - setting flag");1468resized = true;1469}14701471void NativeSetRestarting() {1472restarting = true;1473}14741475bool NativeIsRestarting() {1476return restarting;1477}14781479void NativeShutdown() {1480Achievements::Shutdown();14811482if (g_Config.bAchievementsEnable) {1483FILE *iconCacheFile = File::OpenCFile(GetSysDirectory(DIRECTORY_CACHE) / "icon.cache", "wb");1484if (iconCacheFile) {1485g_iconCache.SaveToFile(iconCacheFile);1486fclose(iconCacheFile);1487}1488}14891490if (g_screenManager) {1491g_screenManager->shutdown();1492delete g_screenManager;1493g_screenManager = nullptr;1494}14951496g_Config.Save("NativeShutdown");14971498INFO_LOG(Log::System, "NativeShutdown called");14991500g_i18nrepo.LogMissingKeys();15011502ShutdownWebServer();15031504#if PPSSPP_PLATFORM(ANDROID)1505System_ExitApp();1506#endif15071508g_PortManager.Shutdown();15091510net::Shutdown();15111512g_Discord.Shutdown();15131514ShaderTranslationShutdown();15151516// Avoid shutting this down when restarting core.1517if (!restarting)1518LogManager::Shutdown();15191520if (logger) {1521delete logger;1522logger = nullptr;1523}15241525g_threadManager.Teardown();15261527#if !(PPSSPP_PLATFORM(ANDROID) || PPSSPP_PLATFORM(IOS))1528System_ExitApp();1529#endif15301531// Previously we did exit() here on Android but that makes it hard to do things like restart on backend change.1532// I think we handle most globals correctly or correct-enough now.1533}15341535// In the future, we might make this more sophisticated, such as storing in the app private directory on Android.1536// Right now we just store secrets in separate files next to ppsspp.ini. The important thing is keeping them out of it1537// since we often ask people to post or send the ini for debugging.1538static Path GetSecretPath(std::string_view nameOfSecret) {1539return GetSysDirectory(DIRECTORY_SYSTEM) / ("ppsspp_" + std::string(nameOfSecret) + ".dat");1540}15411542// name should be simple alphanumerics to avoid problems on Windows.1543bool NativeSaveSecret(std::string_view nameOfSecret, std::string_view data) {1544Path path = GetSecretPath(nameOfSecret);1545if (data.empty() && File::Exists(path)) {1546return File::Delete(path);1547} else if (!File::WriteDataToFile(false, data.data(), data.size(), path)) {1548WARN_LOG(Log::System, "Failed to write secret '%.*s' to path '%s'", (int)nameOfSecret.size(), nameOfSecret.data(), path.c_str());1549return false;1550}1551return true;1552}15531554// On failure, returns an empty string. Good enough since any real secret is non-empty.1555std::string NativeLoadSecret(std::string_view nameOfSecret) {1556Path path = GetSecretPath(nameOfSecret);1557std::string data;1558if (!File::ReadBinaryFileToString(path, &data)) {1559data.clear(); // just to be sure.1560}1561return data;1562}156315641565