GamepadEmu: bounds-check touch pointer IDs before use

TouchInput::id was used directly to index the global primaryButton[]
array (MultiTouchButton::Touch) and to shift pointer bitmasks
(PSPDpad/PSPStick/PSPCustomStick/GestureGamepad::Touch), guarded only
by a debug-only assert in one of the five call sites - a no-op in
release builds. input.id isn't always a small sequential slot in
[0, TOUCH_MAX_POINTERS): SDL assigns SDL_FingerID values directly,
Android pointer IDs can go up to 31, and UWP's TouchMapper allocates
one more slot (11) than TOUCH_MAX_POINTERS (10) and can also return -1
when it runs out of slots - all reachable through ordinary multi-touch
use, no malicious input required.

Also apply bounds check to the PER_GAME gesture config ints
(iDoubleTapGesture/iSwipeUp/Down/Left/Right) before indexing
GestureKey::keyList[] with them.

Additionally, minor cleanup on Android and moves the TouchMapper helper
out from UWP to InputState.h.
This commit is contained in:
Henrik Rydgård committed 2026-08-12 09:47:13 +02:00
1 parent 8f5e984238
commit 9315c0953a
6 files changed
+96 -50

No files matched your search

+43
View File
@@ -6,6 +6,7 @@
#include <unordered_map>
#include <vector>
#include <string>
#include <cstdint>
#include "Common/Common.h"
#include "Common/Input/KeyCodes.h"
@@ -225,3 +226,45 @@ int GetAnalogYDirection(InputDeviceID deviceId);
// Gross hack unfortunately.
extern bool g_IsMappingMouseInput;
// General helper for backends that use different types of touch IDs.
// Warning: If it can't map, IDs come out as -1.
class TouchMapper {
public:
struct Touch {
bool inUse = false;
uint64_t uid = 0;
};
int TouchId(uint64_t touch) {
for (int touchIx = 0; touchIx < maxTouches; touchIx++)
if (touches[touchIx].inUse && touches[touchIx].uid == touch)
return touchIx;
return -1;
}
int AddNewTouch(uint64_t touch) {
for (int touchIx = 0; touchIx < maxTouches; touchIx++) {
if (!touches[touchIx].inUse) {
touches[touchIx].inUse = true;
touches[touchIx].uid = touch;
return touchIx;
}
}
return -1;
}
int RemoveTouch(unsigned touch) {
for (int touchIx = 0; touchIx < maxTouches; touchIx++) {
if (touches[touchIx].inUse && touches[touchIx].uid == touch) {
touches[touchIx].inUse = false;
return touchIx;
}
}
return -1;
}
private:
enum { maxTouches = 10 };
Touch touches[maxTouches]{};
};
+1 -1
View File
@@ -133,7 +133,7 @@ bool CreateSysDirectories() {
INFO_LOG(Log::IO, "Creating '%s' and subdirs:", pspDir.c_str());
File::CreateFullPath(pspDir);
if (!File::Exists(pspDir)) {
INFO_LOG(Log::IO, "Not a workable memstick directory. Giving up");
INFO_LOG(Log::IO, "Not a workable memstick directory (%s). Giving up for now", pspDir.c_str());
return false;
}
+42 -7
View File
@@ -131,7 +131,12 @@ bool MultiTouchButton::CanGlide() const {
}
bool MultiTouchButton::Touch(const TouchInput &input) {
_dbg_assert_(input.id >= 0 && input.id < TOUCH_MAX_POINTERS);
// input.id comes from platform-specific touch/pointer IDs. Most of our
// platforms try to remap to [0, TOUCH_MAX_POINTERS), and all will be updated
// to do that in the future.
if (input.id < 0 || input.id >= TOUCH_MAX_POINTERS) {
return false;
}
bool retval = GamepadComponent::Touch(input);
if ((input.flags & TouchInputFlags::DOWN) && bounds_.Contains(input.x, input.y)) {
@@ -347,6 +352,11 @@ void PSPDpad::GetContentDimensions(const UIContext &dc, float &w, float &h) cons
}
bool PSPDpad::Touch(const TouchInput &input) {
// See the comment in MultiTouchButton::Touch().
if (input.id < 0 || input.id >= TOUCH_MAX_POINTERS) {
return false;
}
bool retval = GamepadComponent::Touch(input);
if (input.flags & TouchInputFlags::DOWN) {
@@ -540,6 +550,11 @@ void PSPStick::Draw(UIContext &dc) {
}
bool PSPStick::Touch(const TouchInput &input) {
// See the comment in MultiTouchButton::Touch().
if (input.id < 0 || input.id >= TOUCH_MAX_POINTERS) {
return false;
}
bool retval = GamepadComponent::Touch(input);
if (input.flags & TouchInputFlags::RELEASE_ALL) {
dragPointerId_ = -1;
@@ -652,6 +667,10 @@ void PSPCustomStick::Draw(UIContext &dc) {
}
bool PSPCustomStick::Touch(const TouchInput &input) {
if (input.id < 0 || input.id >= TOUCH_MAX_POINTERS) {
return false;
}
bool retval = GamepadComponent::Touch(input);
if (input.flags & TouchInputFlags::RELEASE_ALL) {
dragPointerId_ = -1;
@@ -1140,7 +1159,23 @@ GestureGamepad::~GestureGamepad() {
}
}
// The gesture config ints (iDoubleTapGesture/iSwipeUp/Down/Left/Right) are
// PER_GAME config values loaded from an ini with no range clamp applied, unlike
// the UI (a PopupMultiChoice) that normally sets them - a hand-edited, corrupted,
// or version-skewed config could contain an out-of-range value, which would
// otherwise index GestureKey::keyList[] out of bounds below. Mirrors the bounds
// check PSPCustomStick::ProcessTouch already does for its own config->buttons[]
// lookup.
static bool ValidGestureKeyConfig(int config) {
return config > 0 && (size_t)config <= ARRAY_SIZE(GestureKey::keyList);
}
bool GestureGamepad::Touch(const TouchInput &input) {
// See the comment in MultiTouchButton::Touch().
if (input.id < 0 || input.id >= TOUCH_MAX_POINTERS) {
return false;
}
const GestureControlConfig &zone = GetZone();
if (usedPointerMask & (1 << input.id)) {
@@ -1169,7 +1204,7 @@ bool GestureGamepad::Touch(const TouchInput &input) {
const float now = time_now_d();
if (now - lastTapRelease_ < 0.3f && !haveDoubleTapped_) {
if (zone.iDoubleTapGesture != 0 )
if (ValidGestureKeyConfig(zone.iDoubleTapGesture))
controlMapper_->PSPKey(DEVICE_ID_TOUCH, GestureKey::keyList[zone.iDoubleTapGesture - 1], KeyInputFlags::DOWN);
haveDoubleTapped_ = true;
}
@@ -1211,7 +1246,7 @@ bool GestureGamepad::Touch(const TouchInput &input) {
lastTapRelease_ = time_now_d();
if (haveDoubleTapped_) {
if (zone.iDoubleTapGesture != 0)
if (ValidGestureKeyConfig(zone.iDoubleTapGesture))
controlMapper_->PSPKey(DEVICE_ID_TOUCH, GestureKey::keyList[zone.iDoubleTapGesture - 1], KeyInputFlags::UP);
haveDoubleTapped_ = false;
}
@@ -1247,7 +1282,7 @@ void GestureGamepad::Update() {
return;
}
if (GetZone().iSwipeRight != 0) {
if (ValidGestureKeyConfig(GetZone().iSwipeRight)) {
if (dx > th) {
controlMapper_->PSPKey(DEVICE_ID_TOUCH, GestureKey::keyList[GetZone().iSwipeRight - 1], KeyInputFlags::DOWN);
swipeRightReleased_ = false;
@@ -1256,7 +1291,7 @@ void GestureGamepad::Update() {
swipeRightReleased_ = true;
}
}
if (GetZone().iSwipeLeft != 0) {
if (ValidGestureKeyConfig(GetZone().iSwipeLeft)) {
if (dx < -th) {
controlMapper_->PSPKey(DEVICE_ID_TOUCH, GestureKey::keyList[GetZone().iSwipeLeft - 1], KeyInputFlags::DOWN);
swipeLeftReleased_ = false;
@@ -1265,7 +1300,7 @@ void GestureGamepad::Update() {
swipeLeftReleased_ = true;
}
}
if (GetZone().iSwipeUp != 0) {
if (ValidGestureKeyConfig(GetZone().iSwipeUp)) {
if (dy < -th) {
controlMapper_->PSPKey(DEVICE_ID_TOUCH, GestureKey::keyList[GetZone().iSwipeUp - 1], KeyInputFlags::DOWN);
swipeUpReleased_ = false;
@@ -1274,7 +1309,7 @@ void GestureGamepad::Update() {
swipeUpReleased_ = true;
}
}
if (GetZone().iSwipeDown != 0) {
if (ValidGestureKeyConfig(GetZone().iSwipeDown)) {
if (dy > th) {
controlMapper_->PSPKey(DEVICE_ID_TOUCH, GestureKey::keyList[GetZone().iSwipeDown - 1], KeyInputFlags::DOWN);
swipeDownReleased_ = false;
-40
View File
@@ -5,46 +5,6 @@
#include "PPSSPP_UWPMain.h"
namespace UWP {
struct Touch {
bool inUse = false;
unsigned uid;
};
class TouchMapper {
public:
int TouchId(unsigned touch) {
for (int touchIx = 0; touchIx < maxTouches; touchIx++)
if (touches[touchIx].inUse && touches[touchIx].uid == touch)
return touchIx;
return -1;
}
int AddNewTouch(unsigned touch) {
for (int touchIx = 0; touchIx < maxTouches; touchIx++) {
if (!touches[touchIx].inUse) {
touches[touchIx].inUse = true;
touches[touchIx].uid = touch;
return touchIx;
}
}
return -1;
}
int RemoveTouch(unsigned touch) {
for (int touchIx = 0; touchIx < maxTouches; touchIx++) {
if (touches[touchIx].inUse && touches[touchIx].uid == touch) {
touches[touchIx].inUse = false;
return touchIx;
}
}
return -1;
}
private:
enum { maxTouches = 11 };
Touch touches[maxTouches]{};
};
// Main entry point for our app. Connects the app with the Windows shell and handles application lifecycle events.
struct App : winrt::implements<App, winrt::Windows::ApplicationModel::Core::IFrameworkView> {
public:
+4
View File
@@ -1395,6 +1395,8 @@ extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_sendMessageFromJava(JNI
} else if (prm == "local_network") {
INFO_LOG(Log::System, "LOCAL NETWORK PERMISSION: DENIED");
permissions[SYSTEM_PERMISSION_LOCAL_NETWORK] = PERMISSION_STATUS_DENIED;
} else {
WARN_LOG(Log::System, "UNKNOWN PERMISSION GRANTED: %s", prm.c_str());
}
} else if (msg == "permission_granted") {
if (prm == "storage") {
@@ -1403,6 +1405,8 @@ extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_sendMessageFromJava(JNI
} else if (prm == "local_network") {
INFO_LOG(Log::System, "LOCAL NETWORK PERMISSION: GRANTED");
permissions[SYSTEM_PERMISSION_LOCAL_NETWORK] = PERMISSION_STATUS_GRANTED;
} else {
WARN_LOG(Log::System, "UNKNOWN PERMISSION GRANTED: %s", prm.c_str());
}
System_PostUIMessage(UIMessage::PERMISSION_GRANTED, prm);
} else if (msg == "sustained_perf_supported") {
@@ -375,7 +375,11 @@ public class PpssppActivity extends AppCompatActivity implements SensorEventList
// Initialize audio classes. Do this here since detectOptimalAudioSettings()
// needs audioManager
this.audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
this.audioFocusChangeListener = new AudioFocusChangeListener();
if (this.audioManager != null) {
this.audioFocusChangeListener = new AudioFocusChangeListener();
} else {
Log.e(TAG, "Failed to get audio manager, likely not supported on this device.");
}
// Get the optimal buffer sz
detectOptimalAudioSettings();
@@ -1120,7 +1124,7 @@ public class PpssppActivity extends AppCompatActivity implements SensorEventList
// instantiate NativeAudioPlayer
public static void updateAudioFocus(AudioManager audioManager, AudioFocusChangeListener focusChangeListener) {
if (audioManager == null) {
Log.w(TAG, "Couldn't update audio focus, audio manager null");
// Not supported on this device, we logged in init.
return;
}
if (NativeApp.queryConfig("audioMixWithOthers").equals("0")) {