
Se você caiu de paraquedas neste post (e não foi por causa do podcast da GambiConf com o Macabeus), já aproveita e dá uma conferida lá depois. Vale muito a pena.
Caso contrário, como prometido, aqui vai um post pra explicar um experimento com Arduino que fiz lá em 2023.
Mas… por onde começar?
A ideia central desse projeto é não tocar no processo do jogo de forma alguma. Nada de chamadas nativas do Windows pra ler/escrever memória, nada de injeção de DLL, nada disso mesmo. Zero interação direta com o jogo.
Então qual é o truque? Abusar de Machine Learning. Isso mesmo.
Pra esse experimento, usei uma ferramenta online de treino de mira chamada 3D Aim Trainer. A lógica geral é simples: você treina um modelo pra “enxergar” o alvo na tela e reage a isso como um humano faria, só que com ajuda de código.
Pra fazer isso funcionar como um todo, você precisa basicamente de três coisas:
Um programa rodando na sua máquina, responsável por executar o modelo de ML
Um Arduino (flashado na maldade)
E o Arduino apenas simulando cliques e movimentos de mouse, como se fosse um dispositivo legítimo
Nada de falar com o jogo. Só olhar a tela e reagir.
Como cheguei nisso?
Dataset é tudo Primeiro, precisei coletar uma boa quantidade de screenshots da minha tela com o jogo de mira aberto. Essas imagens serviram como dataset pra treinar o modelo usando YOLO (via ultralytics).
Client-side: o cérebro da operação Com o modelo treinado, parti pra parte mais importante: o client. Usei Python (com uma ajudinha do Gepeto) pra rodar o YOLO, capturar screenshots da tela constantemente e processar os resultados em tempo real.
(Os trechos completos de código estão no GitHub no fim da página)
1def capture_screenshot(): 2 monitor = sct.monitors[1] # Primary monitor 3 screenshot = sct.grab(monitor) 4 img = np.array(screenshot) 5 return img, monitor['width'], monitor['height'] 6 7def find_closest_target(centers, screen_w, screen_h): 8 if not centers: 9 return None 10 center_x, center_y = screen_w / 2, screen_h / 2 11 distances = [((cx - center_x)**2 + (cy - center_y)**2, (cx, cy)) for cx, cy in centers] 12 closest = min(distances)[1] # (x, y) tuple 13 return closest 14 15try: 16 while True: 17 frame, screen_w, screen_h = capture_screenshot() 18 19 # YOLO inference 20 results = model(frame, verbose=False) 21 22 centers = [] 23 for result in results: 24 for box in result.boxes: 25 if int(box.cls) == CLASS_ID and float(box.conf) > CONF_THRESHOLD: 26 x1, y1, x2, y2 = map(int, box.xyxy[0]) 27 cx = (x1 + x2) // 2 28 cy = (y1 + y2) // 2 29 centers.append((cx, cy)) 30 31 target = find_closest_target(centers, screen_w, screen_h) 32 33 if target: 34 tx, ty = target 35 cx, cy = screen_w // 2, screen_h // 2 36 dx = int(round(tx - cx)) 37 dy = int(round(ty - cy)) 38 39 print(f"Snapping -> delta ({dx:4d}, {dy:4d})") 40 41 if abs(dx) < MIN_DELTA and abs(dy) < MIN_DELTA: 42 ser.write(b"CLICK\n") 43 ser.flush() 44 print(" - instant click (close enough)") 45 else: 46 remaining_dx = dx 47 remaining_dy = dy 48 49 while abs(remaining_dx) > 0 or abs(remaining_dy) > 0: 50 step_x = max(-MAX_STEP, min(MAX_STEP, remaining_dx)) 51 step_y = max(-MAX_STEP, min(MAX_STEP, remaining_dy)) 52 53 cmd = f"MOVE:{step_x}:{step_y}\n" 54 ser.write(cmd.encode('ascii')) 55 ser.flush() 56 57 remaining_dx -= step_x 58 remaining_dy -= step_y 59 60 time.sleep(0.0005) # 1.5 ms between chunks – very fast but stable 61 62 print(" - snapped & fired") 63 64 time.sleep(0.03) # ~12–15 aims/sec max – prevents spam on same target 65 66 else: 67 time.sleep(0.04) # Faster idle when no targets
Essa parte é literalmente o coração do projeto. O Arduino aqui ainda é coadjuvante.
(Código completo também disponível no GitHub abaixo)
1void loop() { 2 if (Serial.available() > 0) { 3 String line = Serial.readStringUntil('\n'); 4 line.trim(); 5 6 if (line.startsWith("MOVE:")) { 7 int colon = line.indexOf(':', 5); 8 if (colon > 5) { 9 String sx = line.substring(5, colon); 10 String sy = line.substring(colon + 1); 11 12 int dx = sx.toInt(); 13 int dy = sy.toInt(); 14 15 // Chunk if needed - no extra randomness or long delays 16 while (dx != 0 || dy != 0) { 17 int stepX = constrain(dx, -127, 127); 18 int stepY = constrain(dy, -127, 127); 19 20 Mouse.move(stepX, stepY, 0); 21 22 dx -= stepX; 23 dy -= stepY; 24 25 // Minimal delay - just enough to not flood USB (often 0 works, but 1-2 ms safer) 26 delayMicroseconds(800); // ~0.8 ms - adjust to 0 if it still works reliably 27 } 28 } 29 } 30 else if (line == "CLICK") { 31 Mouse.click(MOUSE_LEFT); 32 // No delay here - instant shot 33 } 34 } 35}
Fluxo geral da ideia
Programa (Python)
└─ Captura de tela + processamento ML
└─ Arduino recebe os dados
└─ Simula eventos de mouse
Simples no papel. Nem tanto na prática.
Esse é um projeto experimental, feito com fins educacionais e de pesquisa. Não recomendo, em hipótese alguma, o uso disso pra trapacear ou gerar uma experiência negativa pra outros jogadores.
Dito isso, alguns pontos que ainda faltam ou poderiam ser melhorados:
Atualmente, o projeto toma controle total do mouse. O ideal seria usar um USB Host Shield no Arduino Leonardo, permitindo que você use seu mouse normalmente e apenas dispare eventos sob condições específicas.
O cálculo de distância e snapping ainda não é preciso. Além disso, se o modelo detectar mais de um objeto ao mesmo tempo, pode rolar conflito e o mouse simplesmente enlouquecer.
E um último aviso importante: Hoje em dia, praticamente todo hardware ou dispositivo conectado ao seu computador é detectável pelos anti-cheats modernos. Ou seja, esse projeto é totalmente passível de ban.
Brinque, estude, aprenda, mas com responsabilidade.