mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-01 14:58:14 +00:00
Merge branch 'master' into minor-tweaks
This commit is contained in:
8 files changed
+189
-226
No files matched your search
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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
@@ -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();
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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 + "'");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in new issue
Block a user