• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Нейросеть для игр. Эмуляция мыши на Arduino и исправление ошибок

OverlordGameDev

RAM
Пользователь
Регистрация
14.07.2024
Сообщения
106
Реакции
131

Предисловие:​

В данной статье будет рассказано, как реализовать эмуляцию мыши, используя Arduino, но перед тем, как начать делать эмуляцию через Arduino, хочется рассказать о некоторых поправках в проекте, которые были сделаны вне статей. Правки небольшие, но всё же считаю нужным кратко рассказать о них для полного понимания того, как устроен проект от начала и до конца.

Разделение проекта на разные файлы:​

Первое, что было сделано, — это разделение логики каждой из версий нейронной сети на несколько файлов. Например, раньше в файле cs2.py были функции детекции, создания скриншотов, антиотдачи двух типов, аима двух типов, триггербота. Теперь же всё, что связано с эмуляцией, находится в отдельном файле. Аналогичная ситуация и для версии нейронной сети default.py.

Также функция load_config использовалась практически в каждом файле проекта, и в каждом файле она была продублирована. Было принято решение сделать отдельный файл с функцией для загрузки конфига и во всех остальных файлах проекта просто её вызывать. Таким образом, суммарно стало меньше строк примерно на 100.
Если читали предыдущие статьи, то могли знать, что визуализацию объектов на экране я делал только для версии нейронной сети для игры CS2. Вне статьи был добавлен аналогичный функционал и к нейронной сети default.

Исправление ошибок в интерфейсе:​

Также была проблема в веб-интерфейсе: при загрузке страницы не отображались данные из объектов true/false, например, включения и выключения триггербота для CS2. Проблема заключалась в JS-функции принятия данных из конфига и записи данных в объект. А если точнее, то из конфига брались логические данные, а объект на странице принимает строковые. Чтобы это исправить, нужно принятые из конфига данные конвертировать в строковые.
JavaScript:
document.getElementById('cs2_trigger_bot').value = config.cs2_trigger_bot ? 'true' : 'false';
? 'true' : 'false'; конвертирует логическое значение в строковое. Так необходимо сделать для всех объектов, принимающих true или false.

Вот как выглядит структура проекта на данный момент:
1728160703071.png

Редизайн панели:​

Также был полностью переделан дизайн веб-интерфейса. В самом дизайне ничего особенного и сложного нет, но пару моментов объяснить всё же стоит.
1728160804800.png


Как видно на скриншоте, была добавлена боковая панель. Боковая панель является отдельным HTML-файлом, который вызывается в HTML-файле с основными настройками нейронной сети. Кнопка смены игры расположена именно в файле с боковой панелью, а не в основном файле с настройками. Как могли некоторые подумать, функцию для принятия данных из конфига и отправки данных из объекта выбора игры также нужно будет разместить в файле с боковой панелью, где находится сам объект. Но это не так. По сути, оба эти файла как бы соединяются в один, и функция из файла с основными настройками спокойно будет обновлять данные в объекте из файла с боковой панелью. Но для этого нужно немного изменить код. А для начала рассмотрим файл боковой панели, а точнее, объект с выбором игры:
HTML:
<form id="config-form_game">
   <select class="game_selector" id="game" name="game">
       <option value="cs2">CS2</option>
       <option value="default">DEFAULT</option>
   </select><br>
</form>

Как видно, селектор, то есть объект с выбором игры, находится внутри формы с id config-form_game. Теперь рассмотрим JS-код из файла с основными настройками, где происходит загрузка данных из конфига при открытии и обновлении страницы:
JavaScript:
document.addEventListener("DOMContentLoaded", function() {
   loadConfig();


   const form = document.getElementById("config-form");
   form.addEventListener('input', updateConfig);
});

Как видно, первым делом запускается функция загрузки данных из конфига. Затем создается константа (переменная), в которую записывается объект. В данном случае это объект форма, в котором находятся основные объекты с настройками.
form.addEventListener('input', updateConfig); — данная строка отвечает за вызов ивента, который будет срабатывать при взаимодействии с объектом из ранее записанной переменной (form), и после взаимодействия вызовется функция updateConfig (которая берет данные из объектов, и дальше уже данные попадают в конфиг). Но, как стало понятно, взаимодействие происходит с формой, где находятся основные настройки из основного файла, а не из файла боковой панели. Чтобы это исправить, нужно добавить еще одну константу, но записать в нее форму config-form_game, в которой находится объект выбора игры.
JavaScript:
const formGame = document.getElementById("config-form_game");

Затем нужно вызвать ивент, который будет срабатывать при взаимодействии с объектом, но на этот раз указать переменную не form, а formGame. Таким образом, ивент будет срабатывать при взаимодействии с объектом выбора игры и затем будет вызываться функция для обновления данных в конфиге.
JavaScript:
formGame.addEventListener('input', updateConfig);

Возможно, некоторые могут задаться вопросом, почему бы форму с объектом смены игры просто не сделать с таким же id, как и форма, в которой находятся основные настройки. Но так делать не желательно: указывать одинаковые id у нескольких объектов считается грубой ошибкой, и такой фокус попросту не сработает.

С тем, как обновлять данные из объекта другой страницы, закончено. Теперь можно рассмотреть, как собственно добавляются данные одной страницы на другую.

В данном случае у нас есть два HTML-файла: первый файл — это боковая панель, второй файл — это все настройки нейронной сети. В HTML-файле боковой панели нужно указать это:
HTML:
{% block content %}{% endblock %}
Указать это нужно там, где должны быть данные из второго файла, то есть файла с основными настройками. В данном случае — просто внутри body.

Затем, внутри HTML-файла с настройками нужно указать эту строку:
HTML:
{% extends "base.html" %}
Указать её нужно выше всех объектов, которые должны быть использованы.

Затем нужно указать эту строку:
HTML:
{% block content %}
Указать её нужно под строкой, которая была указана выше. Она означает начало блока с контентом для переноса.

В конце файла с настройками, а именно там, где заканчиваются объекты и JS-функции, нужно указать эту строку:
HTML:
{% endblock %}
Эта строка означает конец блока с контентом, который будет перенесён.

В итоге, когда используется {% extends "base.html" %} в файле с настройками, это означает, что он будет наследовать файл с боковой панелью и вставлять свои объекты и другой код, находящийся внутри {% block content %}{% endblock %}, в место, указанное в base.html, то есть в {% block content %}{% endblock %}, который определён в файле с боковой панелью.

С использованием контента из разных файлов в одном разобрались. Теперь считаю нужным рассказать, как сделать в боковой панели кнопки, при нажатии на которые открываются страницы с настройками разных нейросетей. Как уже стало понятно, есть несколько страниц: страница боковой панели, страница с настройками нейросети для CS и страница с настройками нейросети default.
В Python-файле, в котором находится инициализация Flask, нужно указать маршруты до этих страниц.
Python:
@app.route('/cs2')
def cs2():
   return render_template('cs2.html')


@app.route('/default')
def default():
   return render_template('default.html')

Далее нужно перейти в HTML-файл боковой панели и создать объект <a></a>. Это объект для создания гиперссылок, то есть при нажатии на странице на этот объект будет происходить переход на ссылку, указанную внутри этого объекта.
HTML:
<a href="{{ url_for('default') }}" class="sidebar_item">
   <h4 class="sidebar_text">Default</h4>
</a>


<a href="{{ url_for('cs2') }}" class="sidebar_item">
   <h4 class="sidebar_text">CS2</h4>
</a>
Если не понятно, то внутри href указывается маршрут до страницы, который ранее был прописан в Python-файле. h4 — это просто очередной тип объекта для отображения текста; таких объектов полно на любой вкус.

На этом разбор всех изменений, сделанных вне статьи, закончен. Старался сделать его как можно компактнее, но при этом понятным, если вдруг кому-то интересен абсолютно каждый шаг при разработке нейронной сети.

Работа с Arduino и обновление Python кода:​

Теперь можно приступить к работе с Arduino и к обновлению Python-логики. Для начала нужно выяснить, какая Arduino подойдет.

Какой Arduino подойдет:​

Подходящими Arduino являются те, которые поддерживают подключение к компьютеру как HID-устройство (Human Interface Device). То есть мышка, клавиатура, руль и т.д. А способны на такое платы с микроконтроллером ATmega32u4. В списке таких плат находятся Leonardo, Pro Micro, Micro. Также есть возможность некоторые другие версии Arduino перепрошить на работу как HID-устройство, но в данном случае будет использоваться Arduino Leonardo.
1728161274026.png


После того как было разобрано, какой Arduino потребуется для реализации эмуляции, можно приступать к написанию кода.

Инициализация Arduino в Python коде:​

Первым делом нужно создать в проекте новый файл, который будет называться arduino_initialization.py. В данном файле будут находиться две функции: функция поиска подключенного Arduino и функция подключения к Arduino

Функция поиска Arduino:​

Первой в списке на реализацию будет функция поиска подключенного к ПК Arduino. Функция будет называться find_arduino_port().

Для того чтобы найти подключенный Arduino, нужно пройтись по всем портам компьютера и найти тот, к которому подключено устройство с названием Arduino. Чтобы реализовать данную механику проверки портов, нужно импортировать библиотеку pyserial. Эта библиотека как раз и предназначена для работы с портами.
Python:
import serial
import serial.tools.list_ports

Теперь можно приступить к функции, и первое, что в ней нужно сделать, так это добавить переменную, в которой будет вызываться функция из библиотеки pyserial.
Python:
ports = serial.tools.list_ports.comports()
Данная функция получает список всех доступных на ПК портов.

Затем нужно создать цикл for, который будет перебирать каждый из портов и проверять его на то, чтобы подключенное к порту устройство было с названием Arduino.
Python:
for port in ports:
   if "Arduino" in port.description or "Arduino" in port.manufacturer:
       return port.device
port.description содержит текстовое описание порта.
port.manufacturer содержит информацию о производителе подключенного к порту устройства.
return port.device возвращает название подключенного к порту устройства.


Далее нужно вне цикла for указать возврат None, если подходящих портов не найдено.
Python:
print("Не удалось найти Arduino.")
return None

Затем нужно вызывать эту функцию. Сделать это нужно просто в файле вне какой-либо функции.
Python:
arduino_port = find_arduino_port()

Функция подключения к Arduino:​

Теперь можно начать реализовывать функцию подключения к найденному Arduino. Функция будет называться connect_to_arduino, в аргументах которой будет указан baud_rate=115200. Данное значение является скоростью, с которой будет реализована передача данных, а именно количеством битов, которые могут быть переданы в секунду.
Python:
def connect_to_arduino(baud_rate=115200):

Затем, внутри функции нужно добавить проверку if, на то чтобы, если arduino_port равняется None, то возвращать None.
Python:
if not arduino_port:
   return None
Таким образом, далее программа не будет подключаться к Arduino, если он не был найден.

Затем нужно внутри функции указать try except. Внутри try будет код для подключения к Arduino, а в except — уведомление, если подключиться не получилось. В try нужно создать объект Serial с такими параметрами, как: название подключенного к порту устройства (Arduino), скорость передачи данных и время ожидания записи данных в Arduino.
Python:
ser = serial.Serial(arduino_port, baud_rate, timeout=1, write_timeout=5)
Таким образом, результат вызова объекта записывается в переменную ser.

Далее можно добавить паузу, для того чтобы соединение успело произойти:
Python:
time.sleep(1)

Затем нужно добавить return, который будет возвращать переменную ser, в которой находится результат вызова объекта Serial.
Python:
print(f"Подключено к {arduino_port} со скоростью {baud_rate}")
return ser

Теперь в except нужно указать возврат None (except — это обработка ошибок, если код внутри try не был выполнен).
Python:
except serial.SerialException as e:
   print(f"Не удалось открыть порт {arduino_port}: {e}")
   return None
serial.SerialException обрабатывает ошибки библиотеки pyserial, например, о том, что устройство уже занято или не удалось подключиться, допустим, по причине неподсоединенной к компьютеру платы.

С функцией подключения к Arduino закончено, теперь эту функцию нужно вызвать:
Python:
ser = connect_to_arduino()

Изменение логики нейросети CS2:​

Теперь можно изменить логику наводки и триггербота для нейросети CS2. Т.к. логика нейросети была разделена на несколько файлов, все функции, работающие с эмуляцией мыши, были вынесены в отдельный Python файл, если точнее, в cs2_mouse_emulated.py.

Изменение функция наведения:​

Первой будет изменена функция aim для наведения на врагов. Нужно найти внутри функции строку с эмуляцией мыши.
Python:
win32api.mouse_event(win32con.MOUSEEVENTF_MOVE, int(aim_step_x), int(aim_step_y), 0, 0)

Затем нужно заменить ее на эти строки:
Python:
command = f"{aim_step_x},{aim_step_y}\n"
ser.write(command.encode())
Переменная command будет хранить координаты для перемещения. \n нужен для того, чтобы в дальнейшем код Arduino понимал, когда заканчивается одна команда и начинается другая команда. Когда дойдет очередь до написания кода на Arduino, об этом будет рассказано подробнее.
ser.write — это метод объекта ser (экземпляра класса serial.Serial), который был создан ранее в Python файле инициализации Arduino внутри функции подключения к Arduino.
command.encode() означает, что берется строка из переменной command и конвертируется в байты.
Таким образом, ser.write берет данные из переменной с данными для перемещения и отправляет на Arduino.

На этом можно было бы уже закончить работу с функцией наводки и начать писать код для самого Arduino, но есть одно НО. Дело в том, что данные на Arduino будут поступать очень быстро и очень много, в связи с чем буфер Arduino будет заполнен, если на него будут беспрерывно поступать команды в течение секунд 5-10, то есть если вы нажмете ЛКМ и не будете ее отпускать долгое время. Из-за этого Arduino не сможет считывать новые данные, и это вызовет краш программы. Чтобы этого избежать, нужно обрабатывать ошибку таймаута записи данных на Arduino и при ее возникновении просто очищать буфер. Чтобы это сделать, нужно добавить код, который был написан ранее для замены эмуляции на отправку команды на Arduino, внутрь try.
Python:
try:
   command = f"{aim_step_x},{aim_step_y}\n"
   ser.write(command.encode())
   time.sleep(load_config("cs2_aim_time_sleep"))

А внутрь except нужно добавить это:
Python:
except serial.SerialTimeoutException:
   print("Превышено время ожидания для записи данных на Arduino")
   ser.reset_output_buffer()
serial.SerialTimeoutException нужен для обработки ошибки, если превышено время чтения или записи данных на Arduino, указанное в параметрах timeout и write_timeout, которые ранее были указаны в функции подключения к Arduino внутри Python файла инициализации Arduino.

ser.reset_input_buffer() сбрасывает входной буфер, чтобы освободить место для новых данных, которые могут поступать на Arduino.

Теперь с функцией наведения закончено, и можно переделать функцию триггербота. Но перед этим хочется уточнить, почему не была изменена функция антиотдачи. Если вы читали предыдущие статьи, то можете помнить, что функция антиотдачи не производит эмуляцию, а лишь берет координаты макроса и передает их в функцию наведения. То есть наведение происходит относительно координат антиотдачи прямо в функции aim, в связи с этим функция антиотдачи не использует эмуляцию, поэтому и редактировать ее не требуется.

После разъяснения можно приступать к изменению функции триггербота.

Изменение функции триггербота:​

Внутри функции нужно найти эти строки эмуляции нажатия и отпускания левой кнопки мыши:
Python:
# Нажатие левой кнопки мыши
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0)
# Отпускание левой кнопки мыши
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, 0, 0, 0, 0)

И заменить их на одну маленькую строчку, отправляющую команду на Arduino.
Python:
ser.write(b'CLICK\n')
В дальнейшем внутри кода Arduino будет проверка на данную команду. Если данная команда получена, то внутри кода Arduino будет происходить эмуляция нажатия и отпускания кнопки.

С Python-кодом закончено, и теперь можно начать писать на самом Arduino.

Подготовка к работе с Arduino:​

Для того чтобы начать писать код, потребуется скачать IDE для Arduino по этой ссылке: https://www.arduino.cc/en/software
После установки IDE подключите свой Arduino и дождитесь, пока установятся драйвера. После установки драйверов можно запускать IDE и выбирать в нем свое подключенное устройство.
1728162138131.png

Стандартные функции для Arduino:​

И первое, что вы увидите в IDE, — это стандартный шаблон кода.
C++:
void setup() {

}

void loop() {

}
P.S. Для Arduino код пишется на C++ с использованием фреймворка Wiring. Некоторые считают это отдельным “особенным” языком, но я считаю, что это все же C++ с минимальными отличиями.
Я в C и C++ не силен, и поэтому пришлось изучать базу, смотреть сторонние сурсы и разбирать их для того, чтобы понять, как устроена его работа. Возможно, в моем коде, который будет предоставлен далее, будут грубые ошибки и плохая оптимизация, но это не отменяет того факта, что он работает. Если у вас будут замечания по коду Arduino, просьба написать об этом в комментариях.

После того как IDE подготовлена, Arduino подключен и обнаружен, можно приступать к написанию кода. Для начала нужно рассмотреть 2 пустых функции из шаблона.
В функции setup нужно указывать код, который будет срабатывать при подключении или перезапуске Arduino.
В функцию loop нужно писать код, который будет выполняться непрерывно во время работы Arduino.

Написание кода на Arduino:​

Теперь нужно разобраться с тем, как будет работать эмуляция мыши. Для этого потребуется библиотека Mouse.h. Данная библиотека встроенная, и качать ее из сторонних источников не потребуется. Чтобы подключить данную библиотеку, нужно в начале кода указать данную строку:
C++:
#include <Mouse.h>

Далее нужно создать пустую переменную типа String, в которую будут записываться команды, поступающие от нейросети.
C++:
String command = "";

Далее нужно настроить соединение Arduino с нейронной сетью и инициализировать библиотеку для эмуляции мыши. Делать это всё нужно внутри функции setup, которая срабатывает при подключении Arduino к компьютеру.
C++:
void setup() {
  Serial.begin(115200);
  while (!Serial);
  Mouse.begin();
}
Первая строка внутри функции запускает соединение между Arduino и компьютером со скоростью обмена данными до 115200 бит в секунду (это максимальная скорость, поддерживаемая Arduino Leonardo, и такая же скорость была указана в Python-коде внутри функции подключения к Arduino). Во второй строке происходит цикл while, ожидающий соединение между Arduino и компьютером. Третья строка инициализирует библиотеку эмуляции мыши.

Далее внутри функции loop нужно создать цикл while, в котором будет проверяться, есть ли в буфере данные, переданные на Arduino:
C++:
void loop() {
  while (Serial.available()) {


  }
}

Теперь внутри цикла нужно вызывать чтение символов из буфера и запись их в переменную.
C++:
char c = Serial.read();
Чтение происходит по одному символу, и т.к. эта строка находится внутри цикла, оно будет читать все символы по одному.

Далее нужно создать проверку if на то, чтобы если в переменной “c” находится \n, значит получена полная команда от нейронной сети. То есть, если нейронная сеть отправляет команду 10,20\n (координаты для перемещения курсора), то Arduino будет считывать эту команду так: 1
0
,
2
0
\n
Таким образом и определяется конец команды благодаря \n.
C++:
if (c == '\n') {


}

Далее нужно указать else, который будет срабатывать, если прочитанный символ не является \n. В else будет добавление полученного символа в переменную string command для того, чтобы из полученных по одному символу собрать цельную команду в виде строки. Например, функция триггербота отправляет команду CLICK\n, Arduino ее получит в таком виде:
C
L
I
C
K
\n
При получении каждого из символов будет проверяться, является ли полученный символ \n; если не является, то символ будет добавляться в переменную command, то есть command = C+L+I+C+K (CLICK).
C++:
else {
  command += c;
}

Далее, внутри if, т.к. получен символ \n и команда закончена, будет происходить логика обработки получившейся команды, которая хранится в переменной command. Первой на очереди будет обработка команды CLICK. Для этого нужно создать проверку if, в которой будет проверяться, равняется ли значение из переменной command слову CLICK.
C++:
if (command == "CLICK") {
      
}

Если равняется, то будет происходить нажатие левой кнопки мыши, затем небольшая пауза и отпускание левой кнопки мыши.
C++:
Mouse.press(MOUSE_LEFT);
delay(50);
Mouse.release(MOUSE_LEFT);

После if с проверкой на команду CLICK нужно создать указать else в котором будет обрабатываться команда с координатами, то есть если значение из переменной command не команда CLICK, то тогда это команда с координатами для эмуляции мыши. Внутри else первым делом нужно найти позицию запятой чтобы в будущем разделить значение из переменной command на две части, в первой части будут координаты x, во второй части будут координаты y
C++:
else {
  int comma = command.indexOf(',');
}

Далее нужно создать также внутри этого else проверку if, которая будет проверять переменную comma, больше ли она нуля или нет. Если больше, то значит, запятая была найдена.
C++:
if (comma > 0) {


}

Далее, внутри if нужно извлекать значение до запятой и назначить это значение в переменную dx типа int.
C++:
int dx = command.substring(0, comma).toInt();
В данном коде берется значение из переменной command, метод substring() используется для получения подстроки из переменной command, 0 означает индекс, с которого начинается подстрока, то есть с первого символа из значения в переменной command, comma означает индекс символа, до которого будет браться значение, то есть до запятой. toInt преобразует полученное значение в целое число int.

Теперь, таким же способом нужно получить значение после запятой и назначить его в переменную dy.
C++:
int dy = command.substring(comma + 1).toInt();
В данном коде берется подстрока, начиная с символа после запятой (comma + 1) и до самого последнего символа. Затем полученное значение также конвертируется в целое число int.

Далее следует строка, в которой будет происходить эмуляция перемещения курсора по координатам из переменных dx и dy.
C++:
Mouse.move(dx, dy, 0);

Напоминаю, весь этот код находится внутри проверки if (c == '\n'), так что следующая строка должна быть также внутри этой проверки, но в самом ее низу вне каких-либо других условий.
C++:
command = "";
Эта строка нужна для того чтобы после выполнения эмуляции курсора или клика мыши очищать переменную command для следующей команды.

Компиляция кода для Arduino:​

Теперь нужно скомпилировать данный код и загрузить его на Arduino. Для компиляции нужно нажать на кнопку, показанную на скриншоте:
1728162573507.png


Далее, для загрузки кода на Arduino нужно нажать соседнюю кнопку в виде стрелочки:
1728162603717.png


P.S. Очень важное замечание. Загрузить код на arduino не получится, если оно используется каким-либо приложением, например, если запущена нейросеть, которая уже умеет подключаться к Arduino. Поэтому потребуется отключить нейросеть; в противном случае ничего не загрузится.

На этом с работой над кодом для Arduino закончено, и нейросеть успешно функционирует в связке с Arduino, но есть одно НО. Дело в том, что Arduino может перемещать курсор только на восьмибитное значение со знаком, то есть от -127 до 127. Если значение для перемещения больше, то в таком случае перемещение будет некорректным. Но это можно исправить, разбивая значения на несколько частей. Например, в функции наведения в нейронной сети используется переменная cs2_aim_step из конфиг-файла, данная переменная используется для того чтобы разделять значение координат до объекта на число, указанное в этой самой переменной. Раньше можно было установить значение 1, и тогда бы курсор переместился за один подход моментально и очень быстро. Также после выполнения эмуляции в функции наведения стоит пауза, которая срабатывает после каждого подхода, и т.к. сейчас нельзя установить всего 1 подход, приходится ставить 2 и больше, из-за чего скорость наведения падает. Но в таком случае можно просто убрать паузу после каждого подхода или установить значение паузы на 0, и в таком случае можно сделать перемещение в несколько этапов и разбить значения для перемещения на несколько частей и при этом практически не потерять скорость наведения.

Пауза в функции наведения:
Python:
time.sleep(load_config("cs2_aim_time_sleep"))

Значения в конфиге для того, чтобы все работало корректно (можно изменить через интерфейс, а не через файл):
JSON:
"cs2_aim_step": 2,
"cs2_aim_time_sleep": 0.0,

Теперь с написанием кода для Arduino закончено, и уже сейчас можно проверить нейросеть CS2 на работоспособность. Но в проекте также присутствует версия нейронной сети default с простой антиотдачей и упрощенной логикой наведения. Делается это аналогично тому, как это реализовано в нейросети CS2, но все же я считаю, что кратко объяснить это стоит. Поэтому сейчас будет показано, как обновить и эту версию для работы с Arduino.

Изменение логики нейросети default:​

Для начала нужно перейти в Python файл, в котором находится логика эмуляции мыши для нейросети default, и в этом файле найти функцию aim. Внутри функции aim нужно найти эти строки кода:
Python:
win32api.mouse_event(win32con.MOUSEEVENTF_MOVE, int(aim_step_x), int(aim_step_y), 0, 0)
time.sleep(load_config("aim_time_sleep"))

Нужно заменить первую строку, которая эмулирует курсор мыши, на строку для создания переменной, в которую будут записаны координаты для эмуляции, и добавить еще одну строку для отправки этой команды на Arduino, аналогично реализации из нейросети для CS2.
Python:
command = f"{aim_step_x},{aim_step_y}\n"
ser.write(command.encode())
time.sleep(load_config("aim_time_sleep"))

Затем получившиеся три строки нужно обернуть в try, а под try указать except, который будет обрабатывать ошибки при записи данных в Arduino, опять же аналогично нейросети CS2.
Python:
except serial.SerialTimeoutException:
   print("Превышено время ожидания для записи данных на Arduino.")
   ser.reset_output_buffer()

С функцией aim закончено, и можно приступать к изменению функции антиотдачи. В версии нейросети для CS2 это не требовалось, так как там функция антиотдачи не перемещала, а лишь передавала координаты в функцию наведения. В этом же случае функция антиотдачи работает отдельно от функции наведения.

Для того чтобы обновить функцию антиотдачи, потребуется найти в этой функции эти строки:
Python:
if 0 > win32api.GetKeyState(win32con.VK_LBUTTON):
   win32api.mouse_event(win32con.MOUSEEVENTF_MOVE, 0, int(load_config("anti_recoil_px")), 0, 0)
   time.sleep(load_config("anti_recoil_time_sleep"))

Вторую строку потребуется заменить на аналогичные строки, как и в функции наведения, с одним лишь отличием в названии переменных, данные из которых будут записаны в команду для отправки.
Python:
if 0 > win32api.GetKeyState(win32con.VK_LBUTTON):
   command = f"0,{int(load_config('anti_recoil_px'))}\n"
   ser.write(command.encode())
   time.sleep(load_config("anti_recoil_time_sleep"))
Как видно в строке с переменной command, первое значение 0 — это значение координат для перемещения по x, а данная упрощенная версия антиотдачи должна просто тянуть курсор вниз по y, поэтому первое значение нулевое, а второе значение (y) берется из переменной anti_recoil_px из конфиг файла проекта.

Далее нужно получившиеся 4 строки обернуть в try, а в except указать ту же самую обработку, что и в функции наведения.
Python:
except serial.SerialTimeoutException:
   print("Превышено время ожидания для записи данных на Arduino.")
   ser.reset_output_buffer()

На этом написание кода для Arduino и адаптация кода всех версий нейронной сети закончено, и посмотреть, как работает в данный момент софт, можно в видеопрезентации по ссылке ниже:

Видео презентация работы нейронной сети в связке с Arduino: https://rutube.ru/video/440ca9c3165ffc3daa5930387fb2c04a/

Статья в виде документа:
https://docs.google.com/document/d/112I1dbRSvDqPAnnOmtcYvQu0xpsnUS2FUucb97pfGpU/edit?usp=sharing

Вывод:​

Переписать нейросеть на эмуляцию курсора мыши, используя Arduino, оказалось не так сложно. Самым сложным в этом было только разобраться в библиотеке эмуляции и в синтаксисе языка, на котором пишется код для Arduino. В основном же данное обновление потребовало минимальное количество знаний, и надеюсь, статья получилась понятной и подробной.

О будущих статьях:​

Раз у меня появился Arduino, хочется написать еще несколько статей по работе с ним. У меня уже есть пару идей о том, какие программы на нем написать, но если у вас есть пожелания, то можете сообщить об этом в комментариях.

Сделано OverlordGameDev специально для форума xss.pro
 
Neural network based bots ⊂ bots
 
For 30 lines of code, you've written 300 lines of explanation. Lot of filler.
 
For 30 lines of code, you've written 300 lines of explanation. Lot of filler.
Это нужно для того что бы каждый мог понять как устроен данный код, собственно для этого и создаются обучающие темы, жаль что ты это не понимаешь
 
Это нужно для того что бы каждый мог понять как устроен данный код, собственно для этого и создаются обучающие темы, жаль что ты это не понимаешь
It's more that you consider the reader to be cognitively impaired.
I have many questions for you, but I need to rush, so let me leave a simple one for you, you could think about answering it meanwhile:
What is the neural network here? If it does exist, just tell us the number of trainable parameters.
 
It's more that you consider the reader to be cognitively impaired.
I have many questions for you, but I need to rush, so let me leave a simple one for you, you could think about answering it meanwhile:
What is the neural network here? If it does exist, just tell us the number of trainable parameters.
Боюсь я не понимаю тебя т.к переводчик криво переводит твои слова. Я не знаю к чему ты решил докопаться, но если ты хочешь получить код с короткими комментариями, тебе нужно идти на гитхаб. Думаю на этом разговор окончен т.к у меня нет желания общаться через переводчик
 
There's no neural network here.
If you are going to explain 30 simple lines of code in 300 lines, better do it without asking money from the admin. Explaining string concatenation, basic arduino, python, hardware that don't qualify for a payment.
Lots of filler explaining Mouse.move(dx, dy, 0) itself or how to interpret serial input/output. A lot of what’s described is common knowledge if you have even a basic grasp of Python and Arduino. Using neural network as a misleading buzzword, just a basic Python-Arduino integration to move the mouse and perform clicks, based on predefined coordinates and trigger events. There is no neural network, AI whatever cool term you want to use.
On the Arduino side, you are listening to the serial port and ysubg simple string parsing for commands and exec mouse clicks using Mouse.h. Both of these are very common beginner-level Arduino and Python projects.
Everything is here simple.

port.description содержит текстовое описание порта.
port.manufacturer содержит информацию о производителе подключенного к порту устройства.
return port.device возвращает название подключенного к порту устройства.
Чтение происходит по одному символу, и т.к. эта строка находится внутри цикла, оно будет читать все символы по одному.

Далее нужно создать проверку if на то, чтобы если в переменной “c” находится \n, значит получена полная команда от нейронной сети. То есть, если нейронная сеть отправляет команду 10,20\n (координаты для перемещения курсора), то Arduino будет считывать эту команду так: 1
0
,
2
0
\n
Таким образом и определяется конец команды благодаря \n.


C++:


if (c == '\n') {


}


Далее нужно указать else, который будет срабатывать, если прочитанный символ не является \n. В else будет добавление полученного символа в переменную string command для того, чтобы из полученных по одному символу собрать цельную команду в виде строки. Например, функция триггербота отправляет команду CLICK\n, Arduino ее получит в таком виде:
C
L
I
C
K
\n
При получении каждого из символов будет проверяться, является ли полученный символ \n; если не является, то символ будет добавляться в переменную command, то есть command = C+L+I+C+K (CLICK).


C++:


else {
command += c;
}


Далее, внутри if, т.к. получен символ \n и команда закончена, будет происходить логика обработки получившейся команды, которая хранится в переменной command. Первой на очереди будет обработка команды CLICK. Для этого нужно создать проверку if, в которой будет проверяться, равняется ли значение из переменной command слову CLICK.


---


Consider this a friendly critique, I myself am interested in game hacking stuff, please write some interesting stuff. And also write something that forum members would be interested in, a good indicator of this is discussion, critique, comments, likes generated. Don't ask for a payment if no one shows interest for whatever reason. It's pointless. The whole point of a rewards system was to incentivize interesting shit for the forum members, not what you or I find interesting but what forum as a whole.

I have critiques about the content itself of your previous article but even without diving into the problems there, look
http://xssforum7mmh3n56inuf2h73hvhnzobi7h2ytb3gvklrfqm7ut3xdnyd.onion/threads/123363/

Добавление визуальной отрисовки в нейросеть и новый режим наведения​

There is no discussion/comments/questions or likes whatsoever there. That should be a good indicator whether the topic is interesting for the forum as a whole which in turn qualifies for any sort of payment.

hackeryaroslav, it's interesting. We are given 3 reactions a day, and you spend all three on me? And likes among your friends in echo chambers?

PS. http://xssforum7mmh3n56inuf2h73hvhnzobi7h2ytb3gvklrfqm7ut3xdnyd.onion/members/377816/#reaction-7
Do you even read stuff on the forum or just here to make money off of "articles"?
 
Пишешь ненужные и избыточные объяснения вместо того что бы писать само-документирующийся код, почитай про это это и про то как коментировать код.

Немного примеров, они глупые но дают понять в чем суть.
Код:
void loop() {
  while (Serial.available()) {


void ArduinoSerialBufferCheckLoop(ArduinoSerialBuffer) {
  while (ArduinoSerialBuffer.available()) {


char c = Serial.read();
char c = ArduinoSerialBuffer.read();



Ключевое - ты должен выразить что тебе надо
тебе надо '\n' или тебе нужен симол завершения строки?
Учись ясно выражать что ты делаешь своми процедурами, классами, выражениями.
Тот кто пытается разобрать код, ему интересно что происходит и для чего это, а не каким именно символом завершается строка или какой код у ошибки.
if (c == '\n') {
if (IsNewLineSymbol(c)
if (IsLineEnd(c)



Mouse.press(MOUSE_LEFT);
delay(50);
Mouse.release(MOUSE_LEFT);


DoMouseClick(MOUSE_LEFT);


DoMouseClick(ButtonID){
Mouse.press(ButtonID);
DoDelayBetweenMouseDownAnUp();
Mouse.release(ButtonID);
}
 
Последнее редактирование:
Пишешь ненужные и избыточные объяснения вместо того что бы писать само-документирующийся код, почитай про это это и про то как коментировать код.

Немного примеров, они глупые но дают понять в чем суть.
Код:
void loop() {
  while (Serial.available()) {


void ArduinoSerialBufferCheckLoop(ArduinoSerialBuffer) {
  while (ArduinoSerialBuffer.available()) {


char c = Serial.read();
char c = ArduinoSerialBuffer.read();



Ключевое - ты должен выразить что тебе надо
тебе надо '\n' или тебе нужен симол завершения строки?
Учись ясно выражать что ты делаешь своми процедурами, классами, выражениями.
Тот кто пытается разобрать код, ему интересно что происходит и для чего это, а не каким именно символом завершается строка или какой код у ошибки.
if (c == '\n') {
if (IsNewLineSymbol(c)
if (IsLineEnd(c)



Mouse.press(MOUSE_LEFT);
delay(50);
Mouse.release(MOUSE_LEFT);


DoMouseClick(MOUSE_LEFT);


DoMouseClick(ButtonID){
Mouse.press(ButtonID);
DoDelayBetweenMouseDownAnUp();
Mouse.release(ButtonID);
}


Пишешь ненужные и избыточные объяснения вместо того что бы писать само-документирующийся код, почитай про это это и про то как коментировать код.

Немного примеров, они глупые но дают понять в чем суть.
Код:
void loop() {
  while (Serial.available()) {


void ArduinoSerialBufferCheckLoop(ArduinoSerialBuffer) {
  while (ArduinoSerialBuffer.available()) {


char c = Serial.read();
char c = ArduinoSerialBuffer.read();



Ключевое - ты должен выразить что тебе надо
тебе надо '\n' или тебе нужен симол завершения строки?
Учись ясно выражать что ты делаешь своми процедурами, классами, выражениями.
Тот кто пытается разобрать код, ему интересно что происходит и для чего это, а не каким именно символом завершается строка или какой код у ошибки.
if (c == '\n') {
if (IsNewLineSymbol(c)
if (IsLineEnd(c)



Mouse.press(MOUSE_LEFT);
delay(50);
Mouse.release(MOUSE_LEFT);


DoMouseClick(MOUSE_LEFT);


DoMouseClick(ButtonID){
Mouse.press(ButtonID);
DoDelayBetweenMouseDownAnUp();
Mouse.release(ButtonID);
}
And this as well. The trigger bot is trash and the control scheme is trash.
Lots of filler explaining Mouse.move(dx, dy, 0) itself or how to interpret serial input/output. A lot of what’s described is common knowledge if you have even a basic grasp of Python and Arduino. Using neural network as a misleading buzzword, just a basic Python-Arduino integration to move the mouse and perform clicks, based on predefined coordinates and trigger events. There is no neural network, AI whatever cool term you want to use.
 
And this as well. The trigger bot is trash and the control scheme is trash.
Важно то что он старается и делает по сути своей интересные вещи. Постепенно он научится делать их хорошо.
 
Важно то что он старается и делает по сути своей интересные вещи. Постепенно он научится делать их хорошо.
Hence, he shouldn't request payments from the forum.
He didn't write anything technically incorrect. Other than his misleading use of 'neural network'.
And there's no point in writing about basic Arduino controls on the forum, there are other forums, github pages he could write for.
 
Hence, he shouldn't request payments from the forum.
He didn't write anything technically incorrect. Other than his misleading use of 'neural network'.
And there's no point in writing about basic Arduino controls on the forum, there are other forums, github pages he could write for.
Вот смотри, лично мне была статья интересна и даже полезна по своей сути, исполение кода мне не так важно.
Далее наш админ прекрасно умеет говорить - нет, тем авторам чьи статьи по его мнению не достаточно хороши для оплаты, и еще он платит свои деньги, имей уважение, ты ведь здесь не налогоплатильщик форума, он ведь не твои налоги тратит, а раз так то твои высказывания по поводу оплат оскорбительны. Представь что твои гости начнут рассуждать про то как ты тратишь свои деньги.
А еще этот парень старается, он интересный, он учится, на самом деле учится, так что лично я ему желаю успехов.
 
Вот смотри, лично мне была статья интересна и даже полезна по своей сути, исполение кода мне не так важно.
Далее наш админ прекрасно умеет говорить - нет, тем авторам чьи статьи по его мнению не достаточно хороши для оплаты, и еще он платит свои деньги, имей уважение, ты ведь здесь не налогоплатильщик форума, он ведь не твои налоги тратит, а раз так то твои высказывания по поводу оплат оскорбительны. Представь что твои гости начнут рассуждать про то как ты тратишь свои деньги.
А еще этот парень старается, он интересный, он учится, на самом деле учится, так что лично я ему желаю успехов.
Ему уже объясняли это. Тролль залетный этот пендос, на него лучше внимания не обращать.
 
Вот смотри, лично мне была статья интересна и даже полезна по своей сути, исполение кода мне не так важно.
Далее наш админ прекрасно умеет говорить - нет, тем авторам чьи статьи по его мнению не достаточно хороши для оплаты, и еще он платит свои деньги, имей уважение, ты ведь здесь не налогоплатильщик форума, он ведь не твои налоги тратит, а раз так то твои высказывания по поводу оплат оскорбительны. Представь что твои гости начнут рассуждать про то как ты тратишь свои деньги.
А еще этот парень старается, он интересный, он учится, на самом деле учится, так что лично я ему желаю успехов.
Ему уже объясняли это. Тролль залетный этот пендос, на него лучше внимания не обращать.
Okay, I will admit that part of my criticism definitely comes from trolling, personal fun, revenge, annoyance reading shit but I put my comments out and that's it. Take it or leave it. Throw a like or a dislike. Who cares? At this point, it's already clear I don't care about dislikes. It's the internet.
The admin doesn't concern about my shit. He's got money, lots of it, he pays, he burns, why would anyone care? Other than being fodder for such creative memes: http://xssforum7mmh3n56inuf2h73hvhnzobi7h2ytb3gvklrfqm7ut3xdnyd.onion/threads/124017/post-873865
And if the authors themselves are butt-hurt, they should either indulge in a technical debate without insults, or ignore altogether.
 
Okay, I will admit that part of my criticism definitely comes from trolling, personal fun, revenge, annoyance reading shit but I put my comments out and that's it. Take it or leave it. Throw a like or a dislike. Who cares? At this point, it's already clear I don't care about dislikes. It's the internet.
The admin doesn't concern about my shit. He's got money, lots of it, he pays, he burns, why would anyone care? Other than being fodder for such creative memes: http://xssforum7mmh3n56inuf2h73hvhnzobi7h2ytb3gvklrfqm7ut3xdnyd.onion/threads/124017/post-873865
And if the authors themselves are butt-hurt, they should either indulge in a technical debate without insults, or ignore altogether.
Картинка огонь! Очень смешно. Жаль лайков свободных нет. Может души у тебя и нет но чувство юмора хорошее. Будь добрее к новичкам которые стараются.
 
Картинка огонь! Очень смешно. Жаль лайков свободных нет. Может души у тебя и нет но чувство юмора хорошее. Будь добрее к новичкам которые стараются.
I will give you one. Likes, dislikes and the absence of them are all alike. A comment/reply/troll/debate/insult in words is much better.

PS. Contrary to popular belief on the forum, I am a very agreeable guy IRL.
 
Пишешь ненужные и избыточные объяснения вместо того что бы писать само-документирующийся код, почитай про это это и про то как коментировать код.

Немного примеров, они глупые но дают понять в чем суть.
Код:
void loop() {
  while (Serial.available()) {


void ArduinoSerialBufferCheckLoop(ArduinoSerialBuffer) {
  while (ArduinoSerialBuffer.available()) {


char c = Serial.read();
char c = ArduinoSerialBuffer.read();



Ключевое - ты должен выразить что тебе надо
тебе надо '\n' или тебе нужен симол завершения строки?
Учись ясно выражать что ты делаешь своми процедурами, классами, выражениями.
Тот кто пытается разобрать код, ему интересно что происходит и для чего это, а не каким именно символом завершается строка или какой код у ошибки.
if (c == '\n') {
if (IsNewLineSymbol(c)
if (IsLineEnd(c)



Mouse.press(MOUSE_LEFT);
delay(50);
Mouse.release(MOUSE_LEFT);


DoMouseClick(MOUSE_LEFT);


DoMouseClick(ButtonID){
Mouse.press(ButtonID);
DoDelayBetweenMouseDownAnUp();
Mouse.release(ButtonID);
}
То есть имеешь в виду мне нужно говорить более правильными терминами например не "Тут надо поставить \n что бы перенести на другую строку" а "Тут надо поставить знак завершения строки"?
Я просто себя особо кодером и не считаю и пишу так что бы я сам все понял через долгое время и лично я с терминами плохо лажу, поэтому и пишу так
 
Пишешь ненужные и избыточные объяснения вместо того что бы писать само-документирующийся код, почитай про это это и про то как коментировать код.

Немного примеров, они глупые но дают понять в чем суть.
Код:
void loop() {
  while (Serial.available()) {


void ArduinoSerialBufferCheckLoop(ArduinoSerialBuffer) {
  while (ArduinoSerialBuffer.available()) {


char c = Serial.read();
char c = ArduinoSerialBuffer.read();



Ключевое - ты должен выразить что тебе надо
тебе надо '\n' или тебе нужен симол завершения строки?
Учись ясно выражать что ты делаешь своми процедурами, классами, выражениями.
Тот кто пытается разобрать код, ему интересно что происходит и для чего это, а не каким именно символом завершается строка или какой код у ошибки.
if (c == '\n') {
if (IsNewLineSymbol(c)
if (IsLineEnd(c)



Mouse.press(MOUSE_LEFT);
delay(50);
Mouse.release(MOUSE_LEFT);


DoMouseClick(MOUSE_LEFT);


DoMouseClick(ButtonID){
Mouse.press(ButtonID);
DoDelayBetweenMouseDownAnUp();
Mouse.release(ButtonID);
}
Я на самом деле и в коде такие же длинные комментарии для себя оставляю. Но я тебя кажется понял, попробую более профессиональными словами объяснять
 
То есть имеешь в виду мне нужно говорить более правильными терминами например не "Тут надо поставить \n что бы перенести на другую строку" а "Тут надо поставить знак завершения строки"?
Я просто себя особо кодером и не считаю и пишу так что бы я сам все понял через долгое время и лично я с терминами плохо лажу, поэтому и пишу так
Ты должен выражать ясно с чем ты имеешь дело а не частности, \n это частность, на другй платформе строка может заканчиваться например \t, это к смысловой нагрузке не имеет отношения вообще, и использовать магические числа\знаки это очень плохой подоход.
Примеры

в коде
status = ApiFoo();
; check for access denied
if status == 5 {....

или

в заголовчном файле
#define ERROR_ACCESS_DENIED 5

в коде
status = ApiFoo();
if status == ERROR_ACCESS_DENIED {....
видишь комент уже не нужен совсем и так по коду ясно, более того если будешь собирать под другую платформу где не 5 а 6 у тебя не будет особых проблем.


или вот
if (frame.left.x < mouse_pos.x && mouse_pos.x < frame.right.x && frame.left.y < mouse_pos.y && mouse_pox.y < frame.right.y) {...
; mouse inside on frame - это дерьмовый коментарий к дерьмовому коду.

или так
cursor_in_frame = frame.left.x < mouse_pos.x && mouse_pos.x < frame.right.x && frame.left.y < mouse_pos.y && mouse_pox.y < frame.right.y;
if (cursor_in_frame) {
коментарий не нужен, я и так ясно выразил что тут происходит, но это тоже не очень хороший пример, точнее даже плохой

вот тк уже лучше
if (IsCursorCoordInFrame(mouse_pos, frame)) {


Нормальные имена + четкое выражение что именно тебе нужно, это очень важно.
Давать имена это сложно, надо уметь делать декомпозицию и четко видеть общую картину.
Нужно понмать что коментарии нужны для описания только того что находится за кадром, то что нельзя понять из кода.
Например.


; Я выяснил что инжект в процесс который прожил менее 3х секунд часто приводит к конфликтам.....
WaitForProcessEnoughTimeAliveForInject(process);
Видишь причина ожидания не очевидна, она за кадром и ее надо коментрировать иначе никак не понять зачем здесь этот код, здесь коментарий оправдан.


Читай книги\статьи про то как давать имена и коментировать - это очень сильно ускорит твое развитие.
 
Ты должен выражать ясно с чем ты имеешь дело а не частности, \n это частность, на другй платформе строка может заканчиваться например \t, это к смысловой нагрузке не имеет отношения вообще, и использовать магические числа\знаки это очень плохой подоход.
Примеры

в коде
status = ApiFoo();
; check for access denied
if status == 5 {....

или

в заголовчном файле
#define ERROR_ACCESS_DENIED 5

в коде
status = ApiFoo();
if status == ERROR_ACCESS_DENIED {....
видишь комент уже не нужен совсем и так по коду ясно, более того если будешь собирать под другую платформу где не 5 а 6 у тебя не будет особых проблем.


или вот
if (frame.left.x < mouse_pos.x && mouse_pos.x < frame.right.x && frame.left.y < mouse_pos.y && mouse_pox.y < frame.right.y) {...
; mouse inside on frame - это дерьмовый коментарий к дерьмовому коду.

или так
cursor_in_frame = frame.left.x < mouse_pos.x && mouse_pos.x < frame.right.x && frame.left.y < mouse_pos.y && mouse_pox.y < frame.right.y;
if (cursor_in_frame) {
коментарий не нужен, я и так ясно выразил что тут происходит, но это тоже не очень хороший пример, точнее даже плохой

вот тк уже лучше
if (IsCursorCoordInFrame(mouse_pos, frame)) {


Нормальные имена + четкое выражение что именно тебе нужно, это очень важно.
Давать имена это сложно, надо уметь делать декомпозицию и четко видеть общую картину.
Нужно понмать что коментарии нужны для описания только того что находится за кадром, то что нельзя понять из кода.
Например.


; Я выяснил что инжект в процесс который прожил менее 3х секунд часто приводит к конфликтам.....
WaitForProcessEnoughTimeAliveForInject(process);
Видишь причина ожидания не очевидна, она за кадром и ее надо коментрировать иначе никак не понять зачем здесь этот код, здесь коментарий оправдан.


Читай книги\статьи про то как давать имена и коментировать - это очень сильно ускорит твое развитие.
Хорошо, я понял. Спасибо за совет
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх