cmd = new ArrayList<>();
+ cmd.add(HostProcess.LINKER);
cmd.add(pactl.getAbsolutePath());
java.util.Collections.addAll(cmd, args);
ProcessBuilder builder = new ProcessBuilder(cmd);
@@ -269,7 +270,7 @@ public class PulseAudioComponent extends SessionPart {
try {
File modules = new File(workingDir, "modules/arm64");
ProcessBuilder builder = new ProcessBuilder(
- pactl.getAbsolutePath(), "suspend-sink", "@DEFAULT_SINK@", Boolean.toString(suspended));
+ HostProcess.LINKER, pactl.getAbsolutePath(), "suspend-sink", "@DEFAULT_SINK@", Boolean.toString(suspended));
builder.directory(workingDir);
builder.redirectErrorStream(true);
builder.redirectOutput(new File("/dev/null"));
diff --git a/app/src/main/java/com/droiddeck/launcher/core/HostProcess.kt b/app/src/main/java/com/droiddeck/launcher/core/HostProcess.kt
index 550d55a52..dd01cdd37 100644
--- a/app/src/main/java/com/droiddeck/launcher/core/HostProcess.kt
+++ b/app/src/main/java/com/droiddeck/launcher/core/HostProcess.kt
@@ -14,6 +14,8 @@ import java.util.function.Consumer
* block the child the moment the pipe filled.
*/
object HostProcess {
+ const val LINKER = "/system/bin/linker64"
+
private const val TAG = "HostProcess"
/**
diff --git a/app/src/main/java/com/droiddeck/launcher/core/WifiDiscovery.kt b/app/src/main/java/com/droiddeck/launcher/core/WifiDiscovery.kt
index 40a6721dc..a4d5d9d76 100644
--- a/app/src/main/java/com/droiddeck/launcher/core/WifiDiscovery.kt
+++ b/app/src/main/java/com/droiddeck/launcher/core/WifiDiscovery.kt
@@ -11,10 +11,10 @@ import com.droiddeck.launcher.session.SessionPrefs
object WifiDiscovery {
val permissions = arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION)
- // targetSdk 28 accepts either grant for Wi-Fi scanning.
- fun permissionGranted(context: Context): Boolean = permissions.any {
- context.checkSelfPermission(it) == PackageManager.PERMISSION_GRANTED
- }
+ fun permissionGranted(context: Context): Boolean =
+ (if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) arrayOf(Manifest.permission.ACCESS_FINE_LOCATION) else permissions).any {
+ context.checkSelfPermission(it) == PackageManager.PERMISSION_GRANTED
+ }
fun locationEnabled(context: Context): Boolean = Build.VERSION.SDK_INT < Build.VERSION_CODES.P ||
(context.getSystemService(Context.LOCATION_SERVICE) as? LocationManager)?.isLocationEnabled == true
diff --git a/app/src/main/java/com/droiddeck/launcher/files/FilePickerActivity.kt b/app/src/main/java/com/droiddeck/launcher/files/FilePickerActivity.kt
index 8f8782719..e47b844d5 100644
--- a/app/src/main/java/com/droiddeck/launcher/files/FilePickerActivity.kt
+++ b/app/src/main/java/com/droiddeck/launcher/files/FilePickerActivity.kt
@@ -36,7 +36,6 @@ class FilePickerActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requestedOrientation = com.droiddeck.launcher.session.SessionPrefs.orientation(this)
- // targetSdk 28: the old storage permission is exactly what lets us list the card.
if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), 1)
}
diff --git a/app/src/main/java/com/droiddeck/launcher/input/SteamControllerBle.kt b/app/src/main/java/com/droiddeck/launcher/input/SteamControllerBle.kt
index 34357e48a..05bd63831 100644
--- a/app/src/main/java/com/droiddeck/launcher/input/SteamControllerBle.kt
+++ b/app/src/main/java/com/droiddeck/launcher/input/SteamControllerBle.kt
@@ -26,7 +26,7 @@ import java.util.UUID
*
* Lizard mode is refreshed while connected; when a session ends, the controller restores it itself.
*/
-@SuppressLint("MissingPermission") // BLUETOOTH is granted at install below targetSdk 31
+@SuppressLint("MissingPermission")
class SteamControllerBle(private val context: Context) {
private val thread = HandlerThread("steam-controller-ble").apply { start() }
private val handler = Handler(thread.looper)
diff --git a/app/src/main/java/com/droiddeck/launcher/runtime/LinuxRuntime.java b/app/src/main/java/com/droiddeck/launcher/runtime/LinuxRuntime.java
index 082bf814e..00eb52bff 100644
--- a/app/src/main/java/com/droiddeck/launcher/runtime/LinuxRuntime.java
+++ b/app/src/main/java/com/droiddeck/launcher/runtime/LinuxRuntime.java
@@ -20,8 +20,7 @@ import java.util.List;
* The glibc arm64 rootfs at {@code files/linuxfs} and the proot invocation that runs a program in
* it as this app's own uid. It is a second runtime beside the Wine imagefs, not a container: no
* Wine, no box64, no FEX. proot is packaged as {@code libproot.so} so the installer places it, with
- * its loader, in the native library directory - the only place an app on targetSdk 28 may execute
- * a file from.
+ * its loader, in the native library directory.
*
* Ported from WinNative's gamescope runtime (GPL-3.0).
*/
diff --git a/app/src/main/java/com/droiddeck/launcher/session/SessionPrefs.kt b/app/src/main/java/com/droiddeck/launcher/session/SessionPrefs.kt
index cb2e1cbd6..8b2c01e54 100644
--- a/app/src/main/java/com/droiddeck/launcher/session/SessionPrefs.kt
+++ b/app/src/main/java/com/droiddeck/launcher/session/SessionPrefs.kt
@@ -259,6 +259,12 @@ object SessionPrefs {
prefs(context).edit().putBoolean("micAsked", true).apply()
}
+ fun bluetoothAsked(context: Context): Boolean = prefs(context).getBoolean("bluetoothAsked", false)
+
+ fun setBluetoothAsked(context: Context) {
+ prefs(context).edit().putBoolean("bluetoothAsked", true).apply()
+ }
+
fun setMicEnabled(context: Context, on: Boolean) {
prefs(context).edit().putBoolean("mic", on).apply()
}
diff --git a/tools/linuxfs/preload/noexec.c b/tools/linuxfs/preload/noexec.c
index 26aa539b7..fba3a4e73 100644
--- a/tools/linuxfs/preload/noexec.c
+++ b/tools/linuxfs/preload/noexec.c
@@ -2,14 +2,18 @@
* noexec.c: FEX runs an x86 program and its libraries where they lie, on noexec shared storage too.
* FEX maps them with the guest's PROT_EXEC, which the kernel refuses for a file on a noexec mount,
* but it never runs those pages itself - it runs its own translation of them - so a mapping or an
- * mprotect refused for that alone is made without PROT_EXEC. Only in FEX's own processes.
+ * mprotect refused for that alone is made without PROT_EXEC.
*/
#define _GNU_SOURCE
#include
#include
#include
#include
+#include
+#include
+#include
#include
+#include
#include
/* NOEXEC_TEST: tools/tests builds it for the host. */
@@ -45,11 +49,60 @@ __attribute__((constructor)) static void noexec_init(void) {
real_mprotect = (int (*)(void *, size_t, int))dlsym(RTLD_NEXT, "mprotect");
}
+static int private_file_mapping(uintptr_t start, uintptr_t end) {
+ int fd = (int)syscall(SYS_openat, AT_FDCWD, "/proc/self/maps", O_RDONLY | O_CLOEXEC);
+ if (fd < 0) return 0;
+ char buf[4096], line[1024];
+ size_t used = 0;
+ int found = 0, done = 0;
+ ssize_t n;
+ while (!done && (n = syscall(SYS_read, fd, buf, sizeof buf)) > 0) {
+ for (ssize_t i = 0; i < n && !done; i++) {
+ if (buf[i] != '\n') {
+ if (used < sizeof line - 1) line[used++] = buf[i];
+ continue;
+ }
+ line[used] = 0;
+ used = 0;
+ char *p = line;
+ uintptr_t lo = strtoull(p, &p, 16), hi = strtoull(p + 1, &p, 16);
+ if (start < lo || start >= hi) continue;
+ done = 1;
+ char *perms = p + 1;
+ if (end > hi || perms[3] != 'p') break;
+ char *path = strchr(perms, '/');
+ struct statvfs vfs;
+ found = path && !(statvfs(path, &vfs) == 0 && (vfs.f_flag & ST_NOEXEC));
+ }
+ }
+ syscall(SYS_close, fd);
+ return found;
+}
+
+static int rebake(void *addr, size_t len, int prot) {
+ if (real_mprotect(addr, len, PROT_READ)) return -1;
+ void *copy = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (copy == MAP_FAILED) return -1;
+ memcpy(copy, addr, len);
+ if (mmap(addr, len, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0) == MAP_FAILED) {
+ munmap(copy, len);
+ return -1;
+ }
+ memcpy(addr, copy, len);
+ munmap(copy, len);
+ __builtin___clear_cache((char *)addr, (char *)addr + len);
+ return real_mprotect(addr, len, prot);
+}
+
int mprotect(void *addr, size_t len, int prot) {
if (!real_mprotect) noexec_init();
int result = real_mprotect(addr, len, prot);
- if (result && errno == EACCES && (prot & PROT_EXEC) && in_fex()) {
- result = real_mprotect(addr, len, prot & ~PROT_EXEC);
+ if (result && errno == EACCES && (prot & PROT_EXEC)) {
+ if (in_fex()) {
+ result = real_mprotect(addr, len, prot & ~PROT_EXEC);
+ } else if (len && private_file_mapping((uintptr_t)addr, (uintptr_t)addr + len)) {
+ result = rebake(addr, len, prot);
+ }
if (result) errno = EACCES;
}
return result;