Подписывайтесь на наш Telegram и не пропускайте важные новости! Перейти

Исходник [Сурс] Valorant Python Triggerbot — Color HSV + GHUB Kernel & OBS Capture

Sloppy
Начинающий
Начинающий
Статус
Оффлайн
Регистрация
13 Фев 2026
Сообщения
505
Реакции
12
Здарова, реверсеры. Глянул тут одну любопытную поделку на питоне, которую позиционируют как «vibecoded» триггер. По факту — это сборная солянка из нормальных наработок, которую не стыдно поковырять, если вы устали от детектных методов захвата экрана и палевного эмулятора мыши.

Основной сок тут в двух вещах:
  1. Юзается ghub_device.dll для кликов. Это kernel-драйвер от логитеков, который Vanguard кушает гораздо охотнее, чем обычный SendInput. Шанс отлететь за «искусственный ввод» значительно ниже.
  2. Захват через GameCapture.dll. По сути, это хук в стиле OBS, который тянет кадры напрямую из процесса. Работает в эксклюзивном фулскрине и обходит многие стандартные проверки на скринкапчу.

Техническая начинка:
  • Детекция по HSV (Hue, Saturation, Value). Есть ползунки для настройки под любой цвет обводки (пурпурный в приоритете).
  • Обработка через OpenCV (если установлен) — это дает буст к скорости. Если нет, костылит через colorsys, но это для мазохистов.
  • Настраиваемый FOV (радиус сканирования вокруг центра экрана).
  • Reaction Delay — можно выставить задержку, чтобы не выглядеть как робот с реакцией 0 мс.
  • Интерфейс на DearPyGui. Выглядит опрятно, не грузит систему.

Для тех, кто решит завести это чудо, не забудьте накатить либы:
Код:
Expand Collapse Copy
pip install mss keyboard pyautogui opencv-python numpy dearpygui
Также рядом со скриптом должны лежать ghub_device.dll и GameCapture.dll, иначе магии не будет.

Важный нюанс по Vanguard:
Автор пишет, что сам не тестил на основе, и это правильно. Сигнатуры этого кода в паблике уже наверняка засвечены. Если будете юзать — меняйте логику работы с памятью или хотя бы перепишите структуры. В текущем виде это отличная база для обучения или создания своего приватного билда «для своих».

Код:
Expand Collapse Copy
import os, sys, time, threading, colorsys, ctypes
from ctypes import *
import numpy as np
import dearpygui.dearpygui as dpg
 
# Optional imports with availability flags
try:
    import cv2;      HAS_CV2 = True
except ImportError:  HAS_CV2 = False
 
try:
    import win32api; HAS_WIN32 = True
except ImportError:  HAS_WIN32 = False
 
try:
    import win32gui; HAS_WIN32GUI = True
except ImportError:  HAS_WIN32GUI = False
 
try:
    import mss as mss_lib; HAS_MSS = True
except ImportError:        HAS_MSS = False
 
try:
    import keyboard as kb; HAS_KEYBOARD = True
except ImportError:        HAS_KEYBOARD = False
 
try:
    import pyautogui
    pyautogui.PAUSE = 0
    pyautogui.FAILSAFE = False
    HAS_PYAUTOGUI = True
except ImportError:  HAS_PYAUTOGUI = False
 
# Windows high-precision timer
try: ctypes.windll.winmm.timeBeginPeriod(1)
except: pass
 
# Screen center cached at startup
_user32  = ctypes.windll.user32
CENTER_X = _user32.GetSystemMetrics(0) // 2
CENTER_Y = _user32.GetSystemMetrics(1) // 2
SCREEN_W = _user32.GetSystemMetrics(0)
SCREEN_H = _user32.GetSystemMetrics(1)
 
# ─────────────────────────────────────────────
# GHUB KERNEL MOUSE DRIVER
# Drop ghub_device.dll next to this script.
# Much harder to detect than SendInput.
# ─────────────────────────────────────────────
class GHUBMouse:
    def __init__(self):
        self.found = False
        dll_path = os.path.abspath("ghub_device.dll")
        if not os.path.exists(dll_path):
            print("[GHUB] ghub_device.dll not found — skipping.")
            return
        try:
            self.lib = ctypes.CDLL(dll_path)
            self.lib.device_open.restype  = ctypes.c_int
            self.lib.moveR.argtypes       = [ctypes.c_int, ctypes.c_int, ctypes.c_bool]
            self.lib.moveR.restype        = ctypes.c_int
            if self.lib.device_open() == 0:
                print("[GHUB] device_open() failed — is GHUB running?")
                return
            # Some builds expose mouse_up, some don't
            self.has_mouse_up = False
            try: _ = self.lib.mouse_up; self.has_mouse_up = True
            except: pass
            self.found = True
            print("[GHUB] Kernel driver loaded.")
        except Exception as e:
            print(f"[GHUB] Load failed: {e}")
 
    def click(self):
        if not self.found: return False
        try:
            if self.has_mouse_up:
                self.lib.mouse_down(1); time.sleep(0.01); self.lib.mouse_up(1)
            else:
                self.lib.mouse_down(1); time.sleep(0.01); self.lib.mouse_down(0)
            return True
        except Exception as e:
            print(f"[GHUB] Click error: {e}"); return False
 
# ─────────────────────────────────────────────
# OBS GAME CAPTURE
# Drop GameCapture.dll next to this script.
# Works in exclusive fullscreen — hooks the
# game process directly like OBS does.
# Requires cv2.
# ─────────────────────────────────────────────
class GCImage(Structure):
    _fields_ = [("width",  c_int32),
                ("height", c_int32),
                ("pitch",  c_int32),
                ("data",   POINTER(c_uint8))]
 
class OBSCapture:
    def __init__(self, window_name):
        self.found = False
        self.frame = None
        dll_path = os.path.abspath("GameCapture.dll")
 
        if not os.path.exists(dll_path):
            print("[OBS] GameCapture.dll not found — falling back to mss.")
            return
        if not HAS_CV2:
            print("[OBS] cv2 required for OBS capture — falling back to mss.")
            return
        if not HAS_WIN32GUI:
            print("[OBS] pywin32 required for OBS capture.")
            return
        try:
            self.lib = ctypes.WinDLL(dll_path)
            self.lib.gc_create.argtypes    = [c_int32, c_int32, c_int32, c_int32, c_char_p]
            self.lib.gc_create.restype     = c_void_p
            self.lib.gc_get_frame.argtypes = [c_void_p, POINTER(GCImage)]
            self.lib.gc_get_frame.restype  = c_int32
            self._img = GCImage()
 
            hwnd = win32gui.FindWindow(None, window_name)
            if hwnd == 0:
                print(f"[OBS] Window '{window_name}' not found.")
                return
 
            self.handle = self.lib.gc_create(
                SCREEN_W, SCREEN_H, SCREEN_W, SCREEN_H,
                window_name.encode("utf-8")
            )
            if not self.handle:
                print("[OBS] gc_create() failed.")
                return
 
            self.found = True
            print("[OBS] Game capture online.")
        except Exception as e:
            print(f"[OBS] Init failed: {e}")
 
    def get_frame(self):
        """Returns full BGRA numpy frame or None."""
        if not self.found: return None
        ok = self.lib.gc_get_frame(self.handle, byref(self._img))
        if not ok or not self._img.data: return None
        size = self._img.height * self._img.pitch
        raw  = string_at(self._img.data, size)
        buf  = np.frombuffer(raw, dtype=np.uint8).reshape((self._img.height, self._img.pitch))
        return buf[:, :(self._img.width * 4)].reshape((self._img.height, self._img.width, 4))
 
# ─────────────────────────────────────────────
# SHARED STATE
# All config the GUI writes to, bot reads from.
# ─────────────────────────────────────────────
state = {
    "running":      False,
    "fov_radius":   150,        # pixels from center to edge of scan circle
    "reaction_ms":  0,          # artificial delay before firing (ms)
    "trigger_key":  "space",    # keyboard key OR "MOUSE1"
    "cooldown":     0.05,       # min seconds between triggers
    "hue_low":      277.0,      # degrees 0-360
    "hue_high":     320.0,
    "sat_low":      0.320,      # 0.0-1.0
    "sat_high":     0.700,
    "val_low":      106.0,      # 0-255
    "val_high":     255.0,
    "use_ghub":     False,      # set True automatically if DLL found
    "use_obs":      False,
    "obs_window":   "",
    "log":          "Ready.",
}
state_lock = threading.Lock()
stop_event = threading.Event()
 
# Hardware singletons
ghub = GHUBMouse()
obs_capture: OBSCapture = None  # created when obs_window is set
 
if ghub.found:
    with state_lock:
        state["use_ghub"] = True
 
# ─────────────────────────────────────────────
# INPUT FIRE
# Handles MOUSE1 vs keyboard keys,
# GHUB vs fallback backends.
# ─────────────────────────────────────────────
def fire_trigger():
    with state_lock:
        key      = state["trigger_key"]
        use_ghub = state["use_ghub"]
 
    if key.upper() == "MOUSE1":
        if use_ghub and ghub.found:
            ghub.click()
        elif HAS_WIN32:
            # win32api fallback
            win32api.mouse_event(0x0002, 0, 0)   # MOUSEEVENTF_LEFTDOWN
            time.sleep(0.01)
            win32api.mouse_event(0x0004, 0, 0)   # MOUSEEVENTF_LEFTUP
        elif HAS_PYAUTOGUI:
            pyautogui.click()
    else:
        if HAS_KEYBOARD:
            kb.send(key)
        elif HAS_PYAUTOGUI:
            pyautogui.press(key)
 
# ─────────────────────────────────────────────
# BOT LOOP
# Runs on background thread.
# Captures FOV region, checks HSV circle mask,
# applies reaction delay, fires.
# ─────────────────────────────────────────────
def bot_loop():
    last_trigger  = 0.0
    cached_fov    = -1
    circle_mask   = None
 
    with mss_lib.mss() as sct:
        while not stop_event.is_set():
 
            # Read state once per frame
            with state_lock:
                running     = state["running"]
                cooldown    = state["cooldown"]
                reaction_ms = state["reaction_ms"]
                fov_r       = state["fov_radius"]
                use_obs     = state["use_obs"]
                hl = state["hue_low"]  / 360.0
                hh = state["hue_high"] / 360.0
                sl = state["sat_low"]
                sh = state["sat_high"]
                vl = state["val_low"]  / 255.0
                vh = state["val_high"] / 255.0
 
            if not running:
                time.sleep(0.01)
                continue
 
            # Recompute circular mask only when FOV radius changes
            if fov_r != cached_fov:
                size = fov_r * 2
                Y, X = np.ogrid[:size, :size]
                circle_mask = (np.sqrt((X - fov_r)**2 + (Y - fov_r)**2) <= fov_r)
                cached_fov  = fov_r
 
            size = fov_r * 2
 
            # ── Capture ──────────────────────────────────────
            img = None
 
            if use_obs and obs_capture and obs_capture.found:
                # OBS path: full frame, slice FOV region from center
                full = obs_capture.get_frame()
                if full is not None:
                    y1 = max(0, CENTER_Y - fov_r)
                    x1 = max(0, CENTER_X - fov_r)
                    img = full[y1:y1+size, x1:x1+size]
            
            if img is None:
                # mss fallback (borderless/windowed fullscreen)
                region = {
                    "top":    CENTER_Y - fov_r,
                    "left":   CENTER_X - fov_r,
                    "width":  size,
                    "height": size,
                }
                try:
                    shot = sct.grab(region)
                    img  = np.frombuffer(shot.bgra, dtype=np.uint8).reshape((size, size, 4))
                except Exception as e:
                    set_log(f"Capture error: {e}")
                    time.sleep(0.1)
                    continue
 
            # ── Color detection ───────────────────────────────
            # img is always BGRA shaped (H, W, 4)
            detected = False
 
            if HAS_CV2:
                # Fast path: vectorised HSV conversion via cv2
                # cv2 HSV: H→0-180, S→0-255, V→0-255
                bgr  = img[:, :, :3]
                hsv  = cv2.cvtColor(bgr, cv2.COLOR_BGR2HSV)
                lo   = np.array([hl * 180, sl * 255, vl * 255], dtype=np.uint8)
                hi   = np.array([hh * 180, sh * 255, vh * 255], dtype=np.uint8)
                cmask = cv2.inRange(hsv, lo, hi)
                detected = bool(np.any(circle_mask & (cmask > 0)))
            else:
                # Slow path: per-pixel colorsys (no cv2 installed)
                rgb     = img[:, :, [2, 1, 0]]   # BGRA → RGB
                pixels  = np.argwhere(circle_mask)
                for (py, px) in pixels:
                    r, g, b = rgb[py, px] / 255.0
                    h, s, v = colorsys.rgb_to_hsv(r, g, b)
                    if hl <= h <= hh and sl <= s <= sh and vl <= v <= vh:
                        detected = True
                        break
 
            # ── Fire ─────────────────────────────────────────
            now = time.perf_counter()
            if detected and (now - last_trigger >= cooldown):
                if reaction_ms > 0:
                    time.sleep(reaction_ms / 1000.0)
                fire_trigger()
                last_trigger = time.perf_counter()
                set_log(f"Triggered at {time.strftime('%H:%M:%S')}")
 
def set_log(msg):
    with state_lock:
        state["log"] = msg
    try:
        dpg.set_value("log_text", msg)
    except: pass
 
# ─────────────────────────────────────────────
# GUI CALLBACKS
# ─────────────────────────────────────────────
def toggle_bot():
    with state_lock:
        state["running"] = not state["running"]
        is_on = state["running"]
    dpg.set_value("status_text", "● ACTIVE" if is_on else "● INACTIVE")
    dpg.configure_item("status_text", color=[0, 220, 80] if is_on else [220, 60, 60])
    dpg.set_item_label("toggle_btn", "Stop Bot" if is_on else "Start Bot")
 
def update_state(sender, app_data, user_data):
    with state_lock:
        state[user_data] = app_data
 
def on_hsv_change(sender, app_data, user_data):
    update_state(sender, app_data, user_data)
    update_preview()
 
def update_preview():
    with state_lock:
        h = ((state["hue_low"] + state["hue_high"]) / 2.0) / 360.0
        s =  (state["sat_low"] + state["sat_high"]) / 2.0
        v =  (state["val_low"] + state["val_high"]) / 2.0 / 255.0
    r, g, b = colorsys.hsv_to_rgb(h, s, v)
    dpg.set_value("color_preview", [int(r*255), int(g*255), int(b*255), 255])
 
def apply_obs_window(sender, app_data, user_data):
    global obs_capture
    window_name = app_data.strip()
    with state_lock:
        state["obs_window"] = window_name
    if window_name:
        obs_capture = OBSCapture(window_name)
        with state_lock:
            state["use_obs"] = obs_capture.found
        dpg.set_value("obs_status",
            "[✓] OBS capture active" if obs_capture.found
            else "[✗] Window not found — using mss")
        dpg.configure_item("obs_status",
            color=[0,200,80] if obs_capture.found else [200,100,60])
 
# ─────────────────────────────────────────────
# GUI BUILD
# ─────────────────────────────────────────────
def build_gui():
    dpg.create_context()
    dpg.create_viewport(title="Triggerbot", width=450, height=680, resizable=False)
    dpg.setup_dearpygui()
 
    with dpg.theme() as global_theme:
        with dpg.theme_component(dpg.mvAll):
            dpg.add_theme_color(dpg.mvThemeCol_WindowBg,       [18, 20, 28])
            dpg.add_theme_color(dpg.mvThemeCol_FrameBg,        [30, 33, 45])
            dpg.add_theme_color(dpg.mvThemeCol_FrameBgHovered, [40, 44, 60])
            dpg.add_theme_color(dpg.mvThemeCol_SliderGrab,     [90, 150, 255])
            dpg.add_theme_color(dpg.mvThemeCol_Button,         [50, 80, 160])
            dpg.add_theme_color(dpg.mvThemeCol_ButtonHovered,  [70, 110, 210])
            dpg.add_theme_color(dpg.mvThemeCol_Header,         [40, 60, 120])
            dpg.add_theme_color(dpg.mvThemeCol_HeaderHovered,  [55, 80, 150])
            dpg.add_theme_color(dpg.mvThemeCol_Text,           [210, 215, 230])
            dpg.add_theme_color(dpg.mvThemeCol_CheckMark,      [90, 150, 255])
            dpg.add_theme_style(dpg.mvStyleVar_FrameRounding,  6)
            dpg.add_theme_style(dpg.mvStyleVar_GrabRounding,   4)
            dpg.add_theme_style(dpg.mvStyleVar_WindowPadding,  12, 12)
            dpg.add_theme_style(dpg.mvStyleVar_ItemSpacing,    8, 6)
    dpg.bind_theme(global_theme)
 
    with dpg.window(label="Triggerbot", tag="main_window",
                    no_close=True, no_move=True, no_resize=True,
                    width=450, height=680, pos=[0, 0]):
 
        # ── Status ───────────────────────────────────────────
        with dpg.group(horizontal=True):
            dpg.add_text("● INACTIVE", tag="status_text", color=[220, 60, 60])
            dpg.add_spacer(width=10)
            dpg.add_text("Ready.", tag="log_text", color=[120, 125, 140])
        dpg.add_separator()
        dpg.add_spacer(height=4)
 
        dpg.add_button(label="Start Bot", tag="toggle_btn",
                       callback=toggle_bot, width=-1, height=36)
        dpg.add_spacer(height=6)
 
        # ── Detection ────────────────────────────────────────
        with dpg.collapsing_header(label="Detection", default_open=True):
            dpg.add_spacer(height=2)
            dpg.add_text(f"Screen center: ({CENTER_X}, {CENTER_Y})",
                         color=[120, 125, 140])
            dpg.add_slider_int(
                label="FOV Radius (px)",
                default_value=state["fov_radius"],
                min_value=5, max_value=600, width=220,
                callback=update_state, user_data="fov_radius")
            dpg.add_slider_int(
                label="Reaction Delay (ms)",
                default_value=state["reaction_ms"],
                min_value=0, max_value=500, width=220,
                callback=update_state, user_data="reaction_ms",
                format="%d ms")
            dpg.add_spacer(height=4)
 
        # ── Trigger ──────────────────────────────────────────
        with dpg.collapsing_header(label="Trigger", default_open=True):
            dpg.add_spacer(height=2)
            dpg.add_text("Key options: 'space', 'f', 'MOUSE1', 'ctrl', etc.",
                         color=[120, 125, 140])
            dpg.add_input_text(
                label="Trigger Key",
                default_value=state["trigger_key"], width=130,
                callback=update_state, user_data="trigger_key")
            dpg.add_slider_float(
                label="Cooldown (s)",
                default_value=state["cooldown"],
                min_value=0.01, max_value=2.0, width=220,
                callback=update_state, user_data="cooldown",
                format="%.3f s")
            dpg.add_spacer(height=4)
 
        # ── Hardware ─────────────────────────────────────────
        with dpg.collapsing_header(label="Hardware", default_open=True):
            dpg.add_spacer(height=2)
 
            # GHUB
            ghub_ok    = ghub.found
            ghub_label = "[✓] ghub_device.dll loaded" if ghub_ok else "[✗] ghub_device.dll not found"
            dpg.add_text(ghub_label, color=[0,200,80] if ghub_ok else [180,60,60])
            dpg.add_checkbox(
                label="Use GHUB driver for MOUSE1",
                default_value=ghub_ok,
                callback=update_state, user_data="use_ghub")
            dpg.add_spacer(height=6)
 
            # OBS
            obs_dll_ok = os.path.exists("GameCapture.dll")
            obs_label  = "[✓] GameCapture.dll found" if obs_dll_ok else "[✗] GameCapture.dll not found"
            dpg.add_text(obs_label, color=[0,200,80] if obs_dll_ok else [180,60,60])
            dpg.add_text("Enter game window title to enable OBS capture:",
                         color=[150,155,170])
            dpg.add_input_text(
                label="Window Name", hint="e.g. Counter-Strike 2",
                default_value=state["obs_window"], width=240,
                callback=apply_obs_window, user_data="obs_window",
                on_enter=True)
            dpg.add_text("[mss fallback active]", tag="obs_status",
                         color=[120,125,140])
            dpg.add_spacer(height=4)
 
        # ── HSV Color Range ──────────────────────────────────
        with dpg.collapsing_header(label="HSV Color Range", default_open=True):
            dpg.add_spacer(height=2)
 
            dpg.add_text("Hue  (0 – 360°)")
            with dpg.group(horizontal=True):
                dpg.add_slider_float(label="Low ##h",
                    default_value=state["hue_low"],
                    min_value=0, max_value=360, width=155,
                    callback=on_hsv_change, user_data="hue_low",  format="%.1f°")
                dpg.add_slider_float(label="High##h",
                    default_value=state["hue_high"],
                    min_value=0, max_value=360, width=155,
                    callback=on_hsv_change, user_data="hue_high", format="%.1f°")
 
            dpg.add_text("Sat  (0.0 – 1.0)")
            with dpg.group(horizontal=True):
                dpg.add_slider_float(label="Low ##s",
                    default_value=state["sat_low"],
                    min_value=0.0, max_value=1.0, width=155,
                    callback=on_hsv_change, user_data="sat_low",  format="%.3f")
                dpg.add_slider_float(label="High##s",
                    default_value=state["sat_high"],
                    min_value=0.0, max_value=1.0, width=155,
                    callback=on_hsv_change, user_data="sat_high", format="%.3f")
 
            dpg.add_text("Val  (0 – 255)")
            with dpg.group(horizontal=True):
                dpg.add_slider_float(label="Low ##v",
                    default_value=state["val_low"],
                    min_value=0, max_value=255, width=155,
                    callback=on_hsv_change, user_data="val_low",  format="%.0f")
                dpg.add_slider_float(label="High##v",
                    default_value=state["val_high"],
                    min_value=0, max_value=255, width=155,
                    callback=on_hsv_change, user_data="val_high", format="%.0f")
 
            dpg.add_spacer(height=6)
            with dpg.group(horizontal=True):
                dpg.add_text("Preview (HSV midpoint):")
                dpg.add_color_button(tag="color_preview",
                                     default_value=[128, 0, 200, 255],
                                     width=24, height=24)
            update_preview()
            dpg.add_spacer(height=4)
 
    dpg.set_primary_window("main_window", True)
 
# ─────────────────────────────────────────────
# ENTRY
# ─────────────────────────────────────────────
if __name__ == "__main__":
    missing = []
    if not HAS_MSS:      missing.append("mss")
    if not HAS_KEYBOARD: missing.append("keyboard")
    if missing:
        print(f"[ERROR] Missing packages: {', '.join(missing)}")
        print(f"Run: pip install {' '.join(missing)}")
        sys.exit(1)
 
    if not HAS_CV2:
        print("[WARN] cv2 not installed — color detection will be slower.")
        print("       Install with: pip install opencv-python")
 
    threading.Thread(target=bot_loop, daemon=True).start()
 
    build_gui()
    dpg.show_viewport()
    dpg.start_dearpygui()
 
    stop_event.set()
    dpg.destroy_context()

В целом, связка OBS Hook + GHUB Driver — это классика легитного читинга в Валоранте. Если прикрутить сюда нормальную рандомизацию таймингов и кастомный драйвер, можно жить довольно долго.

Интересно, как сейчас Vanguard триггерится на такие хуки захвата кадра, если dll не подписана валидным сертом.
 
Назад
Сверху Снизу