Merge branch 'master' into minor-tweaks

This commit is contained in:
Henrik Rydgård authored and GitHub committed 2026-08-07 15:14:47 +02:00
commit cd254c9ec3
8 files changed
+189 -226

No files matched your search

+16 -28
View File
@@ -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) {
-5
View File
@@ -22,7 +22,6 @@
#include <string_view>
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_);
+62 -45
View File
@@ -26,9 +26,12 @@
// All credit goes to him and the official miniupnp project! http://miniupnp.free.fr/
#include <algorithm> // find_if
#include <chrono>
#include <cstring>
#include <string>
#include <thread>
#include <mutex>
#include <condition_variable>
#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<UPnPArgs> 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<UPnPArgs> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::recursive_mutex> upnpGuard(upnpLock);
upnpReqs.push_back({ UPNP_CMD_ADD, protocol, port, intport });
std::lock_guard<std::mutex> 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<std::recursive_mutex> upnpGuard(upnpLock);
upnpReqs.push_back({ UPNP_CMD_REMOVE, protocol, port, port });
std::lock_guard<std::mutex> upnpGuard(g_upnpLock);
g_upnpReqs.push_back({ UPNP_CMD_REMOVE, protocol, port, port });
g_upnpCond.notify_one();
}
void UPnP_Notify() {
g_upnpCond.notify_one();
}
+17 -9
View File
@@ -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();
+6 -1
View File
@@ -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));
+38 -137
View File
@@ -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<String> getSdCardPaths(final Context context) {
// Q is the last version that will support normal file access.
ArrayList<String> list = null;
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
Log.i(TAG, "getSdCardPaths: Trying KitKat method");
list = getSdCardPaths19(context);
}
ArrayList<String> 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<String> 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<String> 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 + "'");
}
});
}
+1 -1
View File
@@ -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()
+49
View File
@@ -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),