- Status
- Offline
- Joined
- Mar 3, 2026
- Messages
- 750
- Reaction score
- 457
The mobile scene is usually filled with trashy P2C scripts, but here is something for the people actually trying to build logic. This is an external aimbot project for Rainbow Six Mobile (R6M) that utilizes screen capture and YOLOv8 inference via TensorFlow Lite.
The setup is entirely external — it captures the screen buffer, runs it through a TFLite model to find operator bboxes, and then abuses Android's Accessibility Service to handle the aiming and triggerbot logic. It even includes a head-offset calculation specifically tuned for R6M operator models.
Technical Breakdown
Aim Logic Configuration
You'll want to mess with the head offset. In R6M, the operator's head usually sits around 28-30% from the top of the bounding box.
Implementation Notes & Risks
— Detection: R6M's anti-cheat is still evolving, but screen-capture methods are generally safer than memory-based internals. However, if the AC starts scanning for MediaProjection or Overlay permissions, this will get flagged.
— Performance: Don't expect 120 FPS inference on a potato. You'll need a device with a decent NPU/GPU to run YOLOv8 in real-time without input lag.
— Model: You need to provide your own 'r6m_operators.tflite' or train one using a dataset of Siege operators.
Drop your thoughts if you manage to optimize the inference loop or find a better way to handle the input without the Accessibility API sluggishness.
The setup is entirely external — it captures the screen buffer, runs it through a TFLite model to find operator bboxes, and then abuses Android's Accessibility Service to handle the aiming and triggerbot logic. It even includes a head-offset calculation specifically tuned for R6M operator models.
Technical Breakdown
- Inference Engine: Uses TensorFlow Lite 2.14.0 with GPU delegation to keep the FPS playable.
- Capture Loop: MediaProjection API downscales the 1080p/4K stream to 720p (1280x720) to minimize the inference bottleneck.
- Targeting Logic: Picking the closest enemy to the crosshair within a configurable FOV (supports separate Hipfire and ADS FOV).
- Input Method: AimAccessibilityService uses canPerformGestures(true) to simulate touch input without needing root/kernel access (though it’s easier to detect if the AC checks for active accessibility services).
Everything you need to get the build going in Android Studio (if you have a machine that can actually run it).
Code:
app/build.gradle.kts:
dependencies {
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.activity:activity-compose:1.8.2")
implementation(platform("androidx.compose:compose-bom:2024.02.00"))
implementation("androidx.compose.ui:ui")
implementation("org.tensorflow:tensorflow-lite:2.14.0")
implementation("org.tensorflow:tensorflow-lite-support:0.4.4")
implementation("org.tensorflow:tensorflow-lite-gpu:2.14.0")
}
Aim Logic Configuration
You'll want to mess with the head offset. In R6M, the operator's head usually sits around 28-30% from the top of the bounding box.
Code:
Project Structure:
app/plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
}
android {
namespace = "com.aim.r6m"
compileSdk = 34
defaultConfig {
applicationId = "com.aim.r6m"
minSdk = 28
targetSdk = 34
versionCode = 1
versionName = "1.0"
}
buildFeatures { compose = true }
composeOptions { kotlinCompilerExtensionVersion = "1.5.8" }
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions { jvmTarget = "17" }
androidResources { noCompress += "tflite" }
}
dependencies {
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.activity:activity-compose:1.8.2")
implementation(platform("androidx.compose:compose-bom:2024.02.00"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.material3:material3")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("org.tensorflow:tensorflow-lite:2.14.0")
implementation("org.tensorflow:tensorflow-lite-support:0.4.4")
implementation("org.tensorflow:tensorflow-lite-gpu:2.14.0")
}
├── build.gradle.kts
├── src/main/
│ ├── AndroidManifest.xml
│ ├── assets/r6m_operators.tflite
│ ├── res/xml/accessibility_config.xml
│ └── java/com/aim/r6m/
│ ├── MainActivity.kt
│ ├── CaptureService.kt
│ ├── TargetDetector.kt
│ ├── AimAccessibilityService.kt
│ ├── FovOverlay.kt
│ ├── OverlayService.kt
│ └── Config.kt
app/build.gradle.kts:
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
}
android {
namespace = "com.aim.r6m"
compileSdk = 34
defaultConfig {
applicationId = "com.aim.r6m"
minSdk = 28
targetSdk = 34
versionCode = 1
versionName = "1.0"
}
buildFeatures { compose = true }
composeOptions { kotlinCompilerExtensionVersion = "1.5.8" }
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions { jvmTarget = "17" }
androidResources { noCompress += "tflite" }
}
dependencies {
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.activity:activity-compose:1.8.2")
implementation(platform("androidx.compose:compose-bom:2024.02.00"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.material3:material3")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("org.tensorflow:tensorflow-lite:2.14.0")
implementation("org.tensorflow:tensorflow-lite-support:0.4.4")
implementation("org.tensorflow:tensorflow-lite-gpu:2.14.0")
}plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
}
android {
namespace = "com.aim.r6m"
compileSdk = 34
defaultConfig {
applicationId = "com.aim.r6m"
minSdk = 28
targetSdk = 34
versionCode = 1
versionName = "1.0"
}
buildFeatures { compose = true }
composeOptions { kotlinCompilerExtensionVersion = "1.5.8" }
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions { jvmTarget = "17" }
androidResources { noCompress += "tflite" }
}
dependencies {
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.activity:activity-compose:1.8.2")
implementation(platform("androidx.compose:compose-bom:2024.02.00"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.material3:material3")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("org.tensorflow:tensorflow-lite:2.14.0")
implementation("org.tensorflow:tensorflow-lite-support:0.4.4")
implementation("org.tensorflow:tensorflow-lite-gpu:2.14.0")
}
AndroidManifest.XML:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application
android:label="R6M Aim"
android:icon="@android:drawable/ic_menu_compass"
android:theme="@android:style/Theme.Material.Light.NoActionBar">
<activity android:name=".MainActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".CaptureService"
android:foregroundServiceType="mediaProjection"
android:exported="false" />
<service
android:name=".OverlayService"
android:exported="false" />
<service
android:name=".AimAccessibilityService"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
android:exported="true">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
<meta-data
android:name="android.accessibilityservice.as"
android:resource="@xml/accessibility_config" />
</service>
</application>
</manifest>
res/xml/accessibility_config.xml:
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFeedbackType="feedbackGeneric"
android:accessibilityFlags="flagDefault"
android:canPerformGestures="true"
android:notificationTimeout="100" />
Config.kt:
package com.aim.r6m
object Config {
data class Settings(
var fovPercent: Float = 25f,
var adsFovPercent: Float = 12f,
var smoothing: Float = 6f,
var headOffset: Float = 0.28f, // R6M operator head sits ~28% from top of bbox
var confidence: Float = 0.40f,
var enabled: Boolean = false,
var adsMode: Boolean = false,
var triggerbot: Boolean = false,
var showFovCircle: Boolean = true
)
val current = Settings()
}
MainActivity.kt:
package com.aim.r6m
import android.app.Activity
import android.content.Intent
import android.media.projection.MediaProjectionManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.Settings as AndroidSettings
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
class MainActivity : ComponentActivity() {
private val projectionLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == Activity.RESULT_OK && result.data != null) {
val intent = Intent(this, CaptureService::class.java).apply {
putExtra("code", result.resultCode)
putExtra("data", result.data)
}
ContextCompat.startForegroundService(this, intent)
startService(Intent(this, OverlayService::class.java))
Config.current.enabled = true
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent { MaterialTheme { SettingsScreen() } }
}
@composable
fun SettingsScreen() {
var fov by remember { mutableStateOf(Config.current.fovPercent) }
var adsFov by remember { mutableStateOf(Config.current.adsFovPercent) }
var smooth by remember { mutableStateOf(Config.current.smoothing) }
var headOff by remember { mutableStateOf(Config.current.headOffset) }
var conf by remember { mutableStateOf(Config.current.confidence) }
var ads by remember { mutableStateOf(Config.current.adsMode) }
var trig by remember { mutableStateOf(Config.current.triggerbot) }
var showCircle by remember { mutableStateOf(Config.current.showFovCircle) }
Column(
Modifier.fillMaxSize().padding(20.dp).verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Text("R6M Aim — Test Build", style = MaterialTheme.typography.headlineSmall)
Button(onClick = {
if (!AndroidSettings.canDrawOverlays(this@MainActivity)) {
startActivity(Intent(
AndroidSettings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:$packageName")
))
return@Button
}
val mpm = getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
projectionLauncher.launch(mpm.createScreenCaptureIntent())
}) { Text("Start Capture") }
Button(onClick = {
stopService(Intent(this@MainActivity, CaptureService::class.java))
stopService(Intent(this@MainActivity, OverlayService::class.java))
Config.current.enabled = false
}) { Text("Stop") }
Button(onClick = {
startActivity(Intent(AndroidSettings.ACTION_ACCESSIBILITY_SETTINGS))
}) { Text("Open Accessibility Settings") }
Divider()
Text("Hipfire FOV: ${fov.toInt()}%")
Slider(value = fov, valueRange = 5f..60f,
onValueChange = { fov = it; Config.current.fovPercent = it })
Text("ADS FOV: ${adsFov.toInt()}%")
Slider(value = adsFov, valueRange = 3f..30f,
onValueChange = { adsFov = it; Config.current.adsFovPercent = it })
Text("Smoothing: ${smooth.toInt()} (lower = snappier)")
Slider(value = smooth, valueRange = 1f..25f,
onValueChange = { smooth = it; Config.current.smoothing = it })
Text("Head Offset: ${"%.2f".format(headOff)} (0=center, 0.4=top of head)")
Slider(value = headOff, valueRange = 0f..0.45f,
onValueChange = { headOff = it; Config.current.headOffset = it })
Text("Confidence Threshold: ${"%.2f".format(conf)}")
Slider(value = conf, valueRange = 0.2f..0.9f,
onValueChange = { conf = it; Config.current.confidence = it })
Row(verticalAlignment = androidx.compose.ui.Alignment.CenterVertically) {
Switch(checked = ads, onCheckedChange = { ads = it; Config.current.adsMode = it })
Spacer(Modifier.width(8.dp)); Text("ADS Mode (tighter FOV)")
}
Row(verticalAlignment = androidx.compose.ui.Alignment.CenterVertically) {
Switch(checked = trig, onCheckedChange = { trig = it; Config.current.triggerbot = it })
Spacer(Modifier.width(8.dp)); Text("Triggerbot")
}
Row(verticalAlignment = androidx.compose.ui.Alignment.CenterVertically) {
Switch(checked = showCircle, onCheckedChange = { showCircle = it; Config.current.showFovCircle = it })
Spacer(Modifier.width(8.dp)); Text("Show FOV Circle")
}
}
}
}
CaptureService.kt:
package com.aim.r6m
import android.app.*
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.PixelFormat
import android.hardware.display.DisplayManager
import android.hardware.display.VirtualDisplay
import android.media.Image
import android.media.ImageReader
import android.media.projection.MediaProjection
import android.media.projection.MediaProjectionManager
import android.os.Build
import android.os.Handler
import android.os.HandlerThread
import android.os.IBinder
import androidx.core.app.NotificationCompat
import kotlin.math.sqrt
class CaptureService : Service() {
private lateinit var projection: MediaProjection
private lateinit var reader: ImageReader
private var virtualDisplay: VirtualDisplay? = null
private val detector by lazy { TargetDetector(this) }
private lateinit var thread: HandlerThread
override fun onBind(intent: Intent?): IBinder? = null
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
startForeground(1, buildNotif())
val code = intent.getIntExtra("code", 0)
val data: Intent = intent.getParcelableExtra("data")!!
val mpm = getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
projection = mpm.getMediaProjection(code, data)
val m = resources.displayMetrics
// downscale capture to 720p for inference speed
val capW = 1280; val capH = (1280f * m.heightPixels / m.widthPixels).toInt()
reader = ImageReader.newInstance(capW, capH, PixelFormat.RGBA_8888, 2)
thread = HandlerThread("cap").also { it.start() }
val handler = Handler(thread.looper)
virtualDisplay = projection.createVirtualDisplay(
"r6m", capW, capH, m.densityDpi,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
reader.surface, null, handler
)
reader.setOnImageAvailableListener({ r ->
if (!Config.current.enabled) { r.acquireLatestImage()?.close(); return@setOnImageAvailableListener }
val img = r.acquireLatestImage() ?: return@setOnImageAvailableListener
try {
val bmp = imageToBitmap(img, capW, capH)
processFrame(bmp, m.widthPixels.toFloat(), m.heightPixels.toFloat(), capW.toFloat(), capH.toFloat())
} finally { img.close() }
}, handler)
return START_STICKY
}
private fun processFrame(bmp: Bitmap, screenW: Float, screenH: Float, capW: Float, capH: Float) {
val cfg = Config.current
val cx = capW / 2f; val cy = capH / 2f
val fovPct = if (cfg.adsMode) cfg.adsFovPercent else cfg.fovPercent
val fovPx = (minOf(capW, capH) * fovPct / 100f) / 2f
val targets = detector.detect(bmp, cfg.confidence)
if (targets.isEmpty()) return
// pick closest enemy in FOV, prefer head-aimed point
val best = targets.mapNotNull { t ->
val aimY = t.y - t.h * cfg.headOffset
val dx = t.x - cx; val dy = aimY - cy
val d = sqrt(dx * dx + dy * dy)
if (d <= fovPx) Triple(t.x, aimY, d) else null
}.minByOrNull { it.third } ?: return
// map capture coords back to screen coords
val sx = best.first * (screenW / capW)
val sy = best.second * (screenH / capH)
AimAccessibilityService.instance?.aimAt(sx, sy, cfg.smoothing, cfg.triggerbot)
}
private fun imageToBitmap(image: Image, w: Int, h: Int): Bitmap {
val plane = image.planes[0]
val buf = plane.buffer
val pixelStride = plane.pixelStride
val rowStride = plane.rowStride
val rowPadding = rowStride - pixelStride * w
val full = Bitmap.createBitmap(w + rowPadding / pixelStride, h, Bitmap.Config.ARGB_8888)
full.copyPixelsFromBuffer(buf)
return Bitmap.createBitmap(full, 0, 0, w, h)
}
private fun buildNotif(): Notification {
val ch = "cap"
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val nm = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
nm.createNotificationChannel(NotificationChannel(ch, "Capture", NotificationManager.IMPORTANCE_LOW))
}
return NotificationCompat.Builder(this, ch)
.setContentTitle("R6M Aim active")
.setSmallIcon(android.R.drawable.ic_menu_compass)
.build()
}
override fun onDestroy() {
super.onDestroy()
virtualDisplay?.release()
reader.close()
if (::projection.isInitialized) projection.stop()
if (::thread.isInitialized) thread.quitSafely()
}
}
TargetDetector.kt:
package com.aim.r6m
import android.content.Context
import android.graphics.Bitmap
import org.tensorflow.lite.Interpreter
import org.tensorflow.lite.gpu.CompatibilityList
import org.tensorflow.lite.gpu.GpuDelegate
import org.tensorflow.lite.support.common.FileUtil
import java.nio.ByteBuffer
import java.nio.ByteOrder
import kotlin.math.max
import kotlin.math.min
data class Target(val x: Float, val y: Float, val w: Float, val h: Float, val conf: Float)
class TargetDetector(ctx: Context) {
private val inputSize = 416 // smaller = faster on mobile
private val interpreter: Interpreter
init {
val opts = Interpreter.Options()
val compat = CompatibilityList()
if (compat.isDelegateSupportedOnThisDevice) {
opts.addDelegate(GpuDelegate(compat.bestOptionsForThisDevice))
} else {
opts.setNumThreads(4)
}
interpreter = Interpreter(FileUtil.loadMappedFile(ctx, "r6m_operators.tflite"), opts)
}
fun detect(bmp: Bitmap, confThreshold: Float): List<Target> {
val scaled = Bitmap.createScaledBitmap(bmp, inputSize, inputSize, true)
val input = preprocess(scaled)
// YOLOv8 output shape [1, 5, 3549] for 416 input, single class (operator)
val numBoxes = 3549
val output = Array(1) { Array(5) { FloatArray(numBoxes) } }
interpreter.run(input, output)
val results = mutableListOf<Target>()
val sx = bmp.width / inputSize.toFloat()
val sy = bmp.height / inputSize.toFloat()
for (i in 0 until numBoxes) {
val conf = output[0][4][i]
if (conf < confThreshold) continue
val cx = output[0][0][i] * sx
val cy = output[0][1][i] * sy
val w = output[0][2][i] * sx
val h = output[0][3][i] * sy
results += Target(cx, cy, w, h, conf)
}
return nms(results, 0.45f)
}
private fun preprocess(bmp: Bitmap): ByteBuffer {
val buf = ByteBuffer.allocateDirect(4 * inputSize * inputSize * 3).order(ByteOrder.nativeOrder())
val px = IntArray(inputSize * inputSize)
bmp.getPixels(px, 0, inputSize, 0, 0, inputSize, inputSize)
for (p in px) {
buf.putFloat(((p shr 16) and 0xFF) / 255f)
buf.putFloat(((p shr 8) and 0xFF) / 255f)
buf.putFloat((p and 0xFF) / 255f)
}
buf.rewind()
return buf
}
private fun nms(boxes: List<Target>, iouThresh: Float): List<Target> {
val sorted = boxes.sortedByDescending { it.conf }.toMutableList()
val keep = mutableListOf<Target>()
while (sorted.isNotEmpty()) {
val a = sorted.removeAt(0)
keep += a
sorted.removeAll { iou(a, it) > iouThresh }
}
return keep
}
private fun iou(a: Target, b: Target): Float {
val ax1 = a.x - a.w/2; val ay1 = a.y - a.h/2
val ax2 = a.x + a.w/2; val ay2 = a.y + a.h/2
val bx1 = b.x - b.w/2; val by1 = b.y - b.h/2
val bx2 = b.x + b.w/2; val by2 = b.y + b.h/2
Implementation Notes & Risks
— Detection: R6M's anti-cheat is still evolving, but screen-capture methods are generally safer than memory-based internals. However, if the AC starts scanning for MediaProjection or Overlay permissions, this will get flagged.
— Performance: Don't expect 120 FPS inference on a potato. You'll need a device with a decent NPU/GPU to run YOLOv8 in real-time without input lag.
— Model: You need to provide your own 'r6m_operators.tflite' or train one using a dataset of Siege operators.
Drop your thoughts if you manage to optimize the inference loop or find a better way to handle the input without the Accessibility API sluggishness.