- Статус
- Оффлайн
- Регистрация
- 13 Фев 2026
- Сообщения
- 703
- Реакции
- 20
Наткнулся на интересную базу для Rainbow Six Mobile. Автор забросил проект из-за слабого железа, но выкатил вполне рабочий скелет для создания AI-аимбота. Это не классическая паста на перехвате пакетов, а современный подход через захват экрана и нейронку.
Техническая начинка:
Что внутри:
В проекте реализован раздельный FOV для стрельбы от бедра и ADS (прицеливания), нормальный смуч (smoothing) и даже зачатки триггербота. Вся математика по поиску ближайшей цели в FOV и расчет смещения на голову (head offset) уже на месте. Код чистый, без лишнего мусора, отлично подойдет как фундамент для своего чита на любую мобильную игру.
Нюансы по детекту:
Так как метод использует Screen Capture и Accessibility Services, это безопаснее, чем лезть в память процесса, но современные античиты (типа того же ACE или защит в Siege) могут триггериться на сам факт использования спец. возможностей или активный оверлей сверху игры. Для легитной игры стоит подкрутить смуч и не выставлять FOV на пол-экрана.
Как завести:
Нужен Android Studio и прямые руки. Не забудьте закинуть свою обученную модель r6m_operators.tflite в assets. Если модель качественная, то бот липнет к противникам очень бодро.
Кто готов допилить это до полноценного релиза или планирует форкнуть под другие мобильные шутеры?
Техническая начинка:
- Стек: Kotlin + Jetpack Compose.
- Детект: TensorFlow Lite + YOLOv8 (модель обучена под операторов R6M).
- Захват: MediaProjection API (стриминг экрана в буфер).
- Аим: Через Accessibility Service (имитация жестов, что позволяет обходить некоторые ограничения песочницы).
- Оптимизация: Инференс на GPU через GpuDelegate, ресайз инпута до 416x416 для стабильного FPS.
Код:
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
Что внутри:
В проекте реализован раздельный FOV для стрельбы от бедра и ADS (прицеливания), нормальный смуч (smoothing) и даже зачатки триггербота. Вся математика по поиску ближайшей цели в FOV и расчет смещения на голову (head offset) уже на месте. Код чистый, без лишнего мусора, отлично подойдет как фундамент для своего чита на любую мобильную игру.
Нюансы по детекту:
Так как метод использует Screen Capture и Accessibility Services, это безопаснее, чем лезть в память процесса, но современные античиты (типа того же ACE или защит в Siege) могут триггериться на сам факт использования спец. возможностей или активный оверлей сверху игры. Для легитной игры стоит подкрутить смуч и не выставлять FOV на пол-экрана.
Как завести:
Нужен Android Studio и прямые руки. Не забудьте закинуть свою обученную модель r6m_operators.tflite в assets. Если модель качественная, то бот липнет к противникам очень бодро.
Кто готов допилить это до полноценного релиза или планирует форкнуть под другие мобильные шутеры?