diff --git a/Common/Data/Encoding/Utf8.cpp b/Common/Data/Encoding/Utf8.cpp index 861c266499..f32c829697 100644 --- a/Common/Data/Encoding/Utf8.cpp +++ b/Common/Data/Encoding/Utf8.cpp @@ -113,35 +113,17 @@ uint32_t u8_nextchar(const char *s, int *index, size_t size) { do { ch = (ch << 6) + (unsigned char)s[i++]; sz++; + // Prevent reading past the offsetsFromUTF8 array (max valid UTF-8 is 4 bytes, array has 6 elements) + if (sz >= 6) { + break; + } } while (i < size && s[i] && ((s[i]) & 0xC0) == 0x80); *index = i; + // Clamp sz to valid range + if (sz > 6) sz = 6; return ch - offsetsFromUTF8[sz - 1]; } -uint32_t u8_nextchar_unsafe(const char *s, int *i) { - uint32_t ch = (unsigned char)s[(*i)++]; - int sz = 1; - if (ch >= 0xF0) { - sz++; - ch &= ~0x10; - } - if (ch >= 0xE0) { - sz++; - ch &= ~0x20; - } - if (ch >= 0xC0) { - sz++; - ch &= ~0xC0; - } - - // Just assume the bytes must be there. This is the logic used on the PSP. - for (int j = 1; j < sz; ++j) { - ch <<= 6; - ch += ((unsigned char)s[(*i)++]) & 0x3F; - } - return ch; -} - void u8_inc(const char *s, int *i) { (void)(isutf(s[++(*i)]) || isutf(s[++(*i)]) || isutf(s[++(*i)]) || ++(*i)); @@ -203,11 +185,13 @@ std::string ConvertWStringToUTF8(const std::wstring &wstr) { } void ConvertUTF8ToWString(wchar_t *dest, size_t destSize, std::string_view source) { + if (destSize == 0) return; int len = (int)source.size(); destSize -= 1; // account for the \0. int size = (int)MultiByteToWideChar(CP_UTF8, 0, source.data(), len, NULL, 0); - MultiByteToWideChar(CP_UTF8, 0, source.data(), len, dest, std::min((int)destSize, size)); - dest[std::min((int)destSize, size)] = 0; + int actualSize = std::min((int)destSize, size); + MultiByteToWideChar(CP_UTF8, 0, source.data(), len, dest, actualSize); + dest[actualSize] = 0; // Write null terminator at the correct position } std::wstring ConvertUTF8ToWString(const std::string_view source) { @@ -240,13 +224,18 @@ std::string ConvertUCS2ToUTF8(const std::u16string &wstr) { std::string SanitizeUTF8(std::string_view utf8string) { UTF8 utf(utf8string); std::string s; + // Check for overflow + if (utf8string.size() > SIZE_MAX / 4) { + ERROR_LOG(Log::Common, "SanitizeUTF8: Input too large"); + return std::string(); + } // Worst case. s.resize(utf8string.size() * 4); // This stops at invalid start bytes. size_t pos = 0; while (!utf.end() && !utf.invalid()) { - int c = utf.next_unsafe(); + int c = utf.next(); pos += UTF8::encode(&s[pos], c); } s.resize(pos); @@ -371,7 +360,6 @@ void ConvertUTF8ToJavaModifiedUTF8(std::string *output, std::string_view input) } } output->resize(out_idx); - _dbg_assert_(output->size() >= input.size()); } std::string NormalizeForSearch(std::string_view input) { diff --git a/Common/Data/Encoding/Utf8.h b/Common/Data/Encoding/Utf8.h index 9a752f8c70..5ad49ed162 100644 --- a/Common/Data/Encoding/Utf8.h +++ b/Common/Data/Encoding/Utf8.h @@ -22,7 +22,6 @@ #include uint32_t u8_nextchar(const char *s, int *i, size_t size); -uint32_t u8_nextchar_unsafe(const char *s, int *i); int u8_wc_toutf8(char *dest, uint32_t ch); void u8_inc(const char *s, int *i); void u8_dec(const char *s, int *i); @@ -52,10 +51,6 @@ public: uint32_t next() { return u8_nextchar(c_, &index_, size_); } - // Allow invalid continuation bytes. - uint32_t next_unsafe() { - return u8_nextchar_unsafe(c_, &index_); - } uint32_t peek() const { int tempIndex = index_; return u8_nextchar(c_, &tempIndex, size_); diff --git a/Core/Util/PortManager.cpp b/Core/Util/PortManager.cpp index fe2fb1debd..083aa0488d 100644 --- a/Core/Util/PortManager.cpp +++ b/Core/Util/PortManager.cpp @@ -26,9 +26,12 @@ // All credit goes to him and the official miniupnp project! http://miniupnp.free.fr/ #include // find_if +#include #include #include #include +#include +#include #include "Common/TimeUtil.h" #include "Common/Data/Text/I18n.h" @@ -43,21 +46,10 @@ #include "Core/Util/PortManager.h" PortManager g_PortManager; -static bool upnpServiceRunning = false; -static std::thread upnpServiceThread; -static std::recursive_mutex upnpLock; -static std::deque upnpReqs; - -PortManager::PortManager(): - m_InitState(UPNP_INITSTATE_NONE), - m_LocalPort(UPNP_LOCAL_PORT_ANY), - m_leaseDuration("43200") { - // Don't call net::Init or similar here, we don't want stuff like that to happen before main. -} - -PortManager::~PortManager() { - // FIXME: On Windows it seems using any UPnP functions in this destructor that gets triggered when exiting PPSSPP will resulting to UPNPCOMMAND_HTTP_ERROR due to early WSACleanup (miniupnpc was getting WSANOTINITIALISED internally) -} +static std::thread g_upnpServiceThread; +static std::mutex g_upnpLock; +static std::condition_variable g_upnpCond; +static std::deque g_upnpReqs; void PortManager::Shutdown() { Clear(); @@ -66,7 +58,7 @@ void PortManager::Shutdown() { } void PortManager::Terminate() { - VERBOSE_LOG(Log::Net, "PortManager::Terminate()"); + DEBUG_LOG(Log::Net, "PortManager::Terminate()"); if (urls) { #ifdef WITH_UPNP FreeUPNPUrls(urls); @@ -100,7 +92,7 @@ bool PortManager::Initialize(const unsigned int timeout) { unsigned char ttl = 2; // defaulting to 2 int error = 0; - VERBOSE_LOG(Log::Net, "PortManager::Initialize(%d)", timeout); + DEBUG_LOG(Log::Net, "PortManager::Initialize(%d)", timeout); if (!g_Config.bEnableUPnP) { ERROR_LOG(Log::Net, "PortManager::Initialize - UPnP is Disabled on Networking Settings"); return false; @@ -195,6 +187,7 @@ bool PortManager::Initialize(const unsigned int timeout) { //m_LocalPort = localport; // We shouldn't keep the right port for the next game reset if we wanted to redetect UPnP m_InitState = UPNP_INITSTATE_DONE; RefreshPortList(); + return true; } @@ -475,20 +468,35 @@ int upnpService(const unsigned int timeout) { INFO_LOG(Log::Net, "UPnPService: Begin of UPnPService Thread"); // Service Loop - while (upnpServiceRunning) { - // Sleep for 1ms for faster response if active, otherwise sleep longer (TODO: Improve on this). - sleep_ms(g_Config.bEnableUPnP ? 1 : 500, "upnp-poll"); + while (true) { + UPnPArgs arg; + bool haveArg; + { + std::unique_lock lock(g_upnpLock); + // Also wake up periodically even with nothing queued (and on UPnP_Notify()), so we + // can retry a failed/disconnected UPnP init or notice the enable setting flipped, + // without needing an explicit Add/Remove request to prod us. + g_upnpCond.wait_for(lock, std::chrono::seconds(5), [] { return !g_upnpReqs.empty(); }); + haveArg = !g_upnpReqs.empty(); + if (haveArg) { + arg = g_upnpReqs.front(); + } + } + + // Exit requests must be handled regardless of whether UPnP is enabled or has + // finished initializing, otherwise shutdown could wait on this thread forever. + if (haveArg && arg.cmd == UPNP_CMD_EXIT) { + std::lock_guard lock(g_upnpLock); + g_upnpReqs.pop_front(); + break; + } // Attempts to reconnect if not connected yet or got disconnected if (g_Config.bEnableUPnP && g_PortManager.GetInitState() == UPNP_INITSTATE_NONE) { g_PortManager.Initialize(timeout); } - if (g_Config.bEnableUPnP && g_PortManager.GetInitState() == UPNP_INITSTATE_DONE && !upnpReqs.empty()) { - upnpLock.lock(); - UPnPArgs arg = upnpReqs.front(); - upnpLock.unlock(); - + if (haveArg && g_Config.bEnableUPnP && g_PortManager.GetInitState() == UPNP_INITSTATE_DONE) { bool ok = true; switch (arg.cmd) { case UPNP_CMD_ADD: @@ -503,9 +511,8 @@ int upnpService(const unsigned int timeout) { // It's only considered failed when disconnected (should be retried when reconnected) if (ok) { - upnpLock.lock(); - upnpReqs.pop_front(); - upnpLock.unlock(); + std::lock_guard lock(g_upnpLock); + g_upnpReqs.pop_front(); } } } @@ -516,36 +523,46 @@ int upnpService(const unsigned int timeout) { } // Should we ingore any leftover UPnP requests? instead of processing it on the next game start - upnpLock.lock(); - upnpReqs.clear(); - upnpLock.unlock(); + { + std::unique_lock lock(g_upnpLock); + g_upnpReqs.clear(); + } INFO_LOG(Log::Net, "UPnPService: End of UPnPService Thread"); return 0; } -void __UPnPInit(const int timeout_ms) { - if (!upnpServiceRunning) { - upnpServiceRunning = true; - upnpServiceThread = std::thread(upnpService, timeout_ms); - } +void __UPnPInit(const unsigned int timeout) { + _dbg_assert_(!g_upnpServiceThread.joinable()); + + g_upnpServiceThread = std::thread(upnpService, timeout); } void __UPnPShutdown() { - if (upnpServiceRunning) { - upnpServiceRunning = false; - if (upnpServiceThread.joinable()) { - upnpServiceThread.join(); - } + _dbg_assert_(g_upnpServiceThread.joinable()); + { + std::lock_guard upnpGuard(g_upnpLock); + g_upnpReqs.push_back({ UPNP_CMD_EXIT }); + g_upnpCond.notify_one(); + } + + if (g_upnpServiceThread.joinable()) { + g_upnpServiceThread.join(); } } void UPnP_Add(const char* protocol, unsigned short port, unsigned short intport) { - std::lock_guard upnpGuard(upnpLock); - upnpReqs.push_back({ UPNP_CMD_ADD, protocol, port, intport }); + std::lock_guard upnpGuard(g_upnpLock); + g_upnpReqs.push_back({ UPNP_CMD_ADD, protocol, port, intport }); + g_upnpCond.notify_one(); } void UPnP_Remove(const char* protocol, unsigned short port) { - std::lock_guard upnpGuard(upnpLock); - upnpReqs.push_back({ UPNP_CMD_REMOVE, protocol, port, port }); + std::lock_guard upnpGuard(g_upnpLock); + g_upnpReqs.push_back({ UPNP_CMD_REMOVE, protocol, port, port }); + g_upnpCond.notify_one(); +} + +void UPnP_Notify() { + g_upnpCond.notify_one(); } diff --git a/Core/Util/PortManager.h b/Core/Util/PortManager.h index b42e3f9e08..2b209bf429 100644 --- a/Core/Util/PortManager.h +++ b/Core/Util/PortManager.h @@ -46,12 +46,18 @@ struct UPnPArgs { #define IP_PROTOCOL_TCP "TCP" #define IP_PROTOCOL_UDP "UDP" -#define UPNP_INITSTATE_NONE 0 -#define UPNP_INITSTATE_BUSY 1 -#define UPNP_INITSTATE_DONE 2 -#define UPNP_CMD_ADD 0 -#define UPNP_CMD_REMOVE 1 +enum { + UPNP_INITSTATE_NONE = 0, + UPNP_INITSTATE_BUSY = 1, + UPNP_INITSTATE_DONE = 2, +}; + +enum { + UPNP_CMD_ADD = 0, + UPNP_CMD_REMOVE = 1, + UPNP_CMD_EXIT = 2, +}; struct UPNPUrls; struct IGDdatas; @@ -70,9 +76,6 @@ struct PortMap { class PortManager { public: - PortManager(); - ~PortManager(); - // Initialize UPnP // timeout: milliseconds to wait for a router to respond (default = 2000 ms) bool Initialize(const unsigned int timeout = 2000); @@ -116,7 +119,7 @@ private: extern PortManager g_PortManager; -void __UPnPInit(const int timeout_ms); +void __UPnPInit(const unsigned int timeout_ms); void __UPnPShutdown(); // Add a port & protocol (TCP, UDP or vendor-defined) to map for forwarding (intport = 0 : same as [external] port) @@ -124,3 +127,8 @@ void UPnP_Add(const char* protocol, unsigned short port, unsigned short intport // Remove a port mapping (external port) void UPnP_Remove(const char* protocol, unsigned short port); + +// Wakes the UPnP service thread immediately, without queuing a request - useful after +// changing the enable setting or similar, so it can (re)connect without waiting for the +// periodic retry. +void UPnP_Notify(); diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index a41d7382b0..fda4041acd 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -79,6 +79,7 @@ #include "Core/HLE/sceUsbCam.h" #include "Core/HLE/sceUsbMic.h" #include "Core/HLE/sceUtility.h" +#include "Core/Util/PortManager.h" #include "GPU/Common/PostShader.h" #include "GPU/GPU.h" @@ -1048,7 +1049,11 @@ void GameSettingsScreen::CreateNetworkingSettings(UI::ViewGroup *networkingSetti dnsServer->SetDisabledPtr(&g_Config.bInfrastructureAutoDNS); networkingSettings->Add(new ItemHeader(n->T("UPnP (port-forwarding)"))); - networkingSettings->Add(new CheckBox(&g_Config.bEnableUPnP, n->T("Enable UPnP", "Enable UPnP (need a few seconds to detect)"))); + networkingSettings->Add(new CheckBox(&g_Config.bEnableUPnP, n->T("Enable UPnP", "Enable UPnP (need a few seconds to detect)")))->OnClick.Add([](UI::EventParams &e) { + // Wake the UPnP service thread immediately so it reacts to the new setting instead + // of waiting for the next periodic retry (or a port request that may never come). + UPnP_Notify(); + }); auto *useOriPort = networkingSettings->Add(new CheckBox(&g_Config.bUPnPUseOriginalPort, n->T("UPnP use original port", "UPnP use original port (Enabled = PSP compatibility)"))); networkingSettings->Add(new SettingHint(n->T("UseOriginalPort Tip", "May not work for all devices or games, see wiki."), useOriPort)); diff --git a/android/src/org/ppsspp/ppsspp/PpssppActivity.java b/android/src/org/ppsspp/ppsspp/PpssppActivity.java index 35c01a2c4f..af56ce6e42 100644 --- a/android/src/org/ppsspp/ppsspp/PpssppActivity.java +++ b/android/src/org/ppsspp/ppsspp/PpssppActivity.java @@ -8,7 +8,6 @@ import android.Manifest; import android.annotation.SuppressLint; import android.app.Activity; import android.app.ActivityManager; -import android.app.AlertDialog; import android.app.ApplicationExitInfo; import android.app.UiModeManager; import android.content.ClipData; @@ -31,12 +30,10 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Environment; -import android.os.Looper; import android.os.PowerManager; import android.provider.MediaStore; import android.text.InputType; import android.util.Log; -import android.database.Cursor; import android.view.Gravity; import android.view.HapticFeedbackConstants; import android.view.InputDevice; @@ -56,6 +53,7 @@ import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; +import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.core.graphics.Insets; import androidx.core.view.DisplayCutoutCompat; @@ -63,9 +61,7 @@ import androidx.core.view.ViewCompat; import androidx.core.view.WindowCompat; import androidx.core.view.WindowInsetsCompat; import androidx.core.view.WindowInsetsControllerCompat; -import androidx.documentfile.provider.DocumentFile; -import java.lang.reflect.Field; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; @@ -219,23 +215,6 @@ public class PpssppActivity extends AppCompatActivity implements SensorEventList } } - String getApplicationLibraryDir(ApplicationInfo application) { - String libdir = null; - try { - // Starting from Android 2.3, nativeLibraryDir is available: - Field field = ApplicationInfo.class.getField("nativeLibraryDir"); - libdir = (String) field.get(application); - } catch (SecurityException | NoSuchFieldException | IllegalArgumentException | - IllegalAccessException e1) { - Log.e(TAG, e1.toString()); - } - if (libdir == null) { - // Fallback for Android < 2.3: - libdir = application.dataDir + "/lib"; - } - return libdir; - } - boolean askForPermissions(String[] permissions, int requestCode) { boolean shouldAsk = false; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { @@ -323,81 +302,38 @@ public class PpssppActivity extends AppCompatActivity implements SensorEventList // Unofficial hacks to get a list of SD cards that are not the main "external storage". private static ArrayList getSdCardPaths(final Context context) { - // Q is the last version that will support normal file access. - ArrayList list = null; - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) { - Log.i(TAG, "getSdCardPaths: Trying KitKat method"); - list = getSdCardPaths19(context); - } + ArrayList result = new ArrayList<>(); + File[] externalFilesDirs = context.getExternalFilesDirs(null); + String primaryRoot = Environment.getExternalStorageDirectory().getAbsolutePath(); - if (list == null) { - Log.i(TAG, "getSdCardPaths: Attempting fallback"); - // Try another method. - File[] fileList = new File("/storage/").listFiles(); - if (fileList != null) { - list = new ArrayList<>(); - for (File file : fileList) { - if (!file.getAbsolutePath().equalsIgnoreCase(Environment.getExternalStorageDirectory().getAbsolutePath()) && file.isDirectory() && file.canRead()) { - list.add(file.getAbsolutePath()); + if (externalFilesDirs != null) { + for (File dir : externalFilesDirs) { + // getExternalFilesDirs() can return a non-null path for a card slot with no + // media inserted, so make sure it's actually mounted before trusting it. + if (dir != null && Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState(dir))) { + String root = getRootOfInnerSdCardFolder(dir); + if (root != null && !root.equalsIgnoreCase(primaryRoot)) { + if (!result.contains(root)) { + Log.i(TAG, "SD card found: " + root); + result.add(root); + } } } - if (list.isEmpty()) { - list = null; - } } } - if (list == null) { + if (result.isEmpty()) { + // Fallback for some very old or unusual devices. String[] varNames = { "EXTERNAL_SDCARD_STORAGE", "SECONDARY_STORAGE" }; for (String var : varNames) { - Log.i(TAG, "getSdCardPaths: Checking env " + var); String secStore = System.getenv(var); if (secStore != null && !secStore.isEmpty()) { - list = new ArrayList<>(); - list.add(secStore); + result.add(secStore); break; } } } - if (list == null) { - return new ArrayList<>(); - } else { - return list; - } - } - - private static ArrayList getSdCardPaths19(final Context context) { - final File[] externalCacheDirs = context.getExternalCacheDirs(); - if (externalCacheDirs == null || externalCacheDirs.length==0) - return null; - if (externalCacheDirs.length == 1) { - if (externalCacheDirs[0] == null) - return null; - final String storageState = Environment.getStorageState(externalCacheDirs[0]); - if (!Environment.MEDIA_MOUNTED.equals(storageState)) - return null; - if (Environment.isExternalStorageEmulated()) - return null; - } - final ArrayList result = new ArrayList<>(); - if (externalCacheDirs.length == 1) - result.add(getRootOfInnerSdCardFolder(externalCacheDirs[0])); - for (int i = 1; i < externalCacheDirs.length; ++i) - { - final File file = externalCacheDirs[i]; - if (file == null) - continue; - final String storageState = Environment.getStorageState(file); - if (Environment.MEDIA_MOUNTED.equals(storageState)) { - String root = getRootOfInnerSdCardFolder(externalCacheDirs[i]); - if (root != null) { - result.add(root); - } - } - } - if (result.isEmpty()) - return null; return result; } @@ -405,21 +341,22 @@ public class PpssppActivity extends AppCompatActivity implements SensorEventList private static String getRootOfInnerSdCardFolder(File file) { if (file == null) return null; - final long totalSpace = file.getTotalSpace(); + File current = file; + final long totalSpace = current.getTotalSpace(); if (totalSpace <= 0) { return null; } while (true) { - final File parentFile = file.getParentFile(); + final File parentFile = current.getParentFile(); if (parentFile == null || !parentFile.canRead()) { break; } if (parentFile.getTotalSpace() != totalSpace) { break; } - file = parentFile; + current = parentFile; } - return file.getAbsolutePath(); + return current.getAbsolutePath(); } private boolean detectOpenGLES20() { @@ -454,17 +391,7 @@ public class PpssppActivity extends AppCompatActivity implements SensorEventList boolean landscape = NativeApp.isLandscape(); Log.d(TAG, "Landscape: " + landscape); - // Get system information - PackageManager packMgmr = getPackageManager(); - String packageName = getPackageName(); - - ApplicationInfo appInfo; - try { - appInfo = packMgmr.getApplicationInfo(packageName, 0); - } catch (PackageManager.NameNotFoundException e) { - e.printStackTrace(); - throw new RuntimeException("Unable to locate assets, aborting..."); - } + ApplicationInfo appInfo = getApplicationInfo(); int deviceType = NativeApp.DEVICE_TYPE_MOBILE; if (isVRDevice()) { @@ -487,7 +414,7 @@ public class PpssppActivity extends AppCompatActivity implements SensorEventList String extStorageDir = Environment.getExternalStorageDirectory().getAbsolutePath(); File externalFiles = this.getExternalFilesDir(null); String externalFilesDir = externalFiles == null ? "" : externalFiles.getAbsolutePath(); - String nativeLibDir = getApplicationLibraryDir(appInfo); + String nativeLibDir = appInfo.nativeLibraryDir; Log.i(TAG, "Ext storage: " + extStorageState + " " + extStorageDir); Log.i(TAG, "Ext files dir: " + externalFilesDir); @@ -573,13 +500,14 @@ public class PpssppActivity extends AppCompatActivity implements SensorEventList } @NonNull - private static String getInstallerName(PackageManager packageManager) { + private String getInstallerName(PackageManager packageManager) { String installerName; + String packageName = getPackageName(); try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) - installerName = packageManager.getInstallSourceInfo("package name").getInstallingPackageName(); + installerName = packageManager.getInstallSourceInfo(packageName).getInstallingPackageName(); else { - installerName = packageManager.getInstallerPackageName("package name"); + installerName = packageManager.getInstallerPackageName(packageName); } if (installerName == null || installerName.isEmpty()) { installerName = "unknown"; @@ -718,24 +646,12 @@ public class PpssppActivity extends AppCompatActivity implements SensorEventList super.onCreate(savedInstanceState); if (m_hasNoNativeBinary) { - new Thread() { - @Override - public void run() { - Looper.prepare(); - AlertDialog.Builder builder = new AlertDialog.Builder(PpssppActivity.this); - builder.setMessage("The native part of PPSSPP for ABI " + Build.CPU_ABI + " is missing. Try downloading an official build?").setTitle("Error starting PPSSPP").create().show(); - Looper.loop(); - } - }.start(); - - try { - Thread.sleep(3000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - // We don't call super.onCreate, we just bail in an ugly way. - System.exit(-1); + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setMessage("The native part of PPSSPP for ABI " + Build.CPU_ABI + " is missing. Try downloading an official build?") + .setTitle("Error starting PPSSPP") + .setPositiveButton("OK", (dialog, which) -> finish()) + .setOnCancelListener(dialog -> finish()) + .show(); return; } @@ -1485,21 +1401,8 @@ public class PpssppActivity extends AppCompatActivity implements SensorEventList Log.i(TAG, "onActivityResult: packedRequest=" + packedRequest + " resultCode=" + resultCode); } - private AlertDialog.Builder createDialogBuilderWithDeviceThemeAndUiVisibility() { - return new AlertDialog.Builder(this, AlertDialog.THEME_DEVICE_DEFAULT_DARK); - } - - @RequiresApi(Build.VERSION_CODES.M) - private AlertDialog.Builder createDialogBuilderNew() { - return new AlertDialog.Builder(this, android.R.style.Theme_Material_Dialog_Alert); - } - private AlertDialog.Builder createDialogBuilder() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - return createDialogBuilderNew(); - } else { - return createDialogBuilderWithDeviceThemeAndUiVisibility(); - } + return new AlertDialog.Builder(this); } // The return value is sent to C++ via requestID. @@ -1990,11 +1893,9 @@ public class PpssppActivity extends AppCompatActivity implements SensorEventList @Keep @SuppressWarnings("unused") public void postCommand(String command, String parameter) { - final String cmd = command; - final String param = parameter; runOnUiThread(() -> { - if (!processCommand(cmd, param)) { - Log.e(TAG, "processCommand failed: cmd: '" + cmd + "' param: '" + param + "'"); + if (!processCommand(command, parameter)) { + Log.e(TAG, "processCommand failed: cmd: '" + command + "' param: '" + parameter + "'"); } }); } diff --git a/ext/discord-rpc-build/CMakeLists.txt b/ext/discord-rpc-build/CMakeLists.txt index 079c06496b..a891f22245 100644 --- a/ext/discord-rpc-build/CMakeLists.txt +++ b/ext/discord-rpc-build/CMakeLists.txt @@ -31,7 +31,7 @@ add_library(discord-rpc STATIC ${ALL_SOURCE_FILES}) target_include_directories(discord-rpc PUBLIC ../discord-rpc/src ../discord-rpc/include ../rapidjson/include) -if(UNIX) +if(UNIX AND NOT ANDROID) target_link_libraries(discord-rpc PUBLIC pthread) endif() diff --git a/unittest/UnitTest.cpp b/unittest/UnitTest.cpp index 3edcd22eb5..4b77ccc20b 100644 --- a/unittest/UnitTest.cpp +++ b/unittest/UnitTest.cpp @@ -417,6 +417,54 @@ bool TestTruncateCpy() { len = truncate_cat(catBuf, sizeof(catBuf), "", 0, "", 0); EXPECT_EQ_INT((int)len, 0); EXPECT_EQ_INT((int)catBuf[0], 0); + return true; +} + +bool TestUtf8() { + // Valid multi-byte UTF-8 (ASCII + 2-byte 'é' + 3-byte '€') round-trips unchanged. + const std::string valid = "abc \xC3\xA9 \xE2\x82\xAC"; + EXPECT_TRUE(SanitizeUTF8(valid) == valid); + + // u8_nextchar must stop at the end of the buffer instead of reading past a + // truncated multi-byte sequence (a lead byte with no continuation bytes). + { + std::string s = "abc"; + s += (char)0xF4; + int index = 3; + int size = (int)s.size(); + uint32_t c = u8_nextchar(s.data(), &index, size); + EXPECT_EQ_INT(index, size); + EXPECT_EQ_INT((int)c, 0xF4); + } + + // A long run of stray continuation bytes must not walk off the end of the + // internal offsetsFromUTF8 table (used to read arbitrarily far out of bounds). + { + std::string s(32, (char)0x80); + int index = 0; + int size = (int)s.size(); + uint32_t c = u8_nextchar(s.data(), &index, size); + EXPECT_TRUE(index > 0 && index <= size); + } + + // SanitizeUTF8 on a string that ends mid-sequence must not read or write past + // the buffer, and must preserve the well-formed leading portion. + { + std::string truncated = "abc"; + truncated += (char)0xF4; + std::string sanitized = SanitizeUTF8(truncated); + EXPECT_TRUE(sanitized.substr(0, 3) == "abc"); + } + + // ConvertUTF8ToJavaModifiedUTF8 must simply drop an incomplete trailing + // sequence rather than asserting or crashing. + { + std::string input = "abc"; + input += (char)0xF0; + std::string output; + ConvertUTF8ToJavaModifiedUTF8(&output, input); + EXPECT_TRUE(output == "abc"); + } return true; } @@ -1518,6 +1566,7 @@ TestItem availableTests[] = { TEST_ITEM(Parsers), TEST_ITEM(TruncateCpy), TEST_ITEM(MemBlockInfoSaveState), + TEST_ITEM(Utf8), TEST_ITEM(IRPassSimplify), TEST_ITEM(Jit), TEST_ITEM(VFPUMatrixTranspose),