Перейти к содержанию

Посоветуйте, как улучшить код


Рекомендуемые сообщения

Пишу на С. Есть острая необходимость в быстром чтении из памяти трех переменных, обработки прочитанных значений и отправки в окно игры имитации нажатия клавиш клавиатуры. При этом нужно, чтобы была возможность включать и отключать эти описанные операции через нажатие горячих клавиш. Например, Ctrl+M включает постоянное чтение, обработку и при определенном условии имитацию нажатия клавиши, а Ctrl+B отключает.
Сейчас реализовано так. Программа запускается, вместе с ней запускается бесконечный цикл while (1) {...}. В нем два блока:
1) Первый блок считывает нажатие зарегистрированных горячих клавиш через if (PeekMessage(&msg, NULL, WM_HOTKEY, WM_HOTKEY, PM_REMOVE) > 0) {...}:
если нажато Ctrl+M, он делает флажок mode истинным, если Ctrl+B - ложным.
2) Второй блок срабатывает, если флажок истинный, через if (mode) {...}. В нем происходит считывание из памяти нескольких значений, их обработка, и при определенном условии в окно игры шлется нажатие клавиши через PostMessage(hWnd, WM_CHAR, 0x00000073, 0x001F0001) (на самом деле PostMessage(...) срабатывает три раза, так как нужны еще WM_KEYDOWN и WM_KEYUP до и после WM_CHAR соответственно, но не суть).
Считывать из памяти нужно действительно быстро: имитация нажатия клавиши должна успеть произойти за 0.2 секунды прежде, чем значение определенной переменной успеет обновиться.
В вопросе оптимизации мне уже не нравится наличие бесконечного цикла, который очень сильно нагружает процессор и большую часть времени зря (зря считывает из памяти, обрабатывает и ничего не нажимает). Это приводит к тормозам и, как следствие, к тому, что сымитировать нажатие, когда надо, моя программа не успевает. Это очень иронично: возможности языка С позволяют считывать значения достаточно быстро, но язык настолько быстрый, что уже даже сама игра тормозит от нагрузки процессора бесконечным циклом.
Заменять ли бесконечный цикл на таймер? Если да, то чем он лучше? Если нет, то нужно ли добавлять Sleep(...) с некоторым значением в общий цикл while (1) {...}?
Если добавлять Sleep(...), то это будет тормозить так же и PeekMessage(...): реже будет отлавливать нажатие горячих клавиш. Тогда, наверное, лучше разбить на два потока: один поток будет обрабатывать нажатие горячих клавиш, а другой - все остальное?
Знаю про функцию GetMessage(...) в качестве альтернативы PeekMessage(...). Она стопит работу другого кода программы, пока не получит сообщение, в данном случае горячие клавиши. Имеет ли смысл помещать ее в этот поток по отлову горячих клавиш на замену PeekMessage(...)?

Ссылка на комментарий
Поделиться на другие сайты

Из того, что я прочитал, я увидел следующую картину - быстрое чтение и выполнение каких-либо действий нужно только тогда, когда mode включен. Если я правильно понимаю, вынесение кода ожидания горячих клавиш в отдельный поток не решит проблему стопроцентной загрузки процессора - тебе всё равно придется добавлять ожидание хотя бы 5 милисекунд (для твоего интервала в 200 милисекунд это не должно быть критично). По остальному - не видя код перед глазами довольно сложно искать пути оптимизации.

Ссылка на комментарий
Поделиться на другие сайты

2 часа назад, Xipho сказал:

Из того, что я прочитал, я увидел следующую картину - быстрое чтение и выполнение каких-либо действий нужно только тогда, когда mode включен. Если я правильно понимаю, вынесение кода ожидания горячих клавиш в отдельный поток не решит проблему стопроцентной загрузки процессора - тебе всё равно придется добавлять ожидание хотя бы 5 милисекунд (для твоего интервала в 200 милисекунд это не должно быть критично). По остальному - не видя код перед глазами довольно сложно искать пути оптимизации.

Вот код:

Спойлер
while (1) {
	if (PeekMessage(&msg, NULL, WM_HOTKEY, WM_HOTKEY, PM_REMOVE) > 0) {
		if (msg.message == WM_HOTKEY) {
			system("cls");
			if (msg.lParam == 0x004D0002) { //'M' is 0x004D0002
				printf("MODE ON\n");
				mode = 1;
				output = 0;
			} else { //'B' is 0x00420002
				printf("MODE OFF\n");
				mode = 0;
			}
		}
	}
	if (mode) {
		if (!(read_bombs() * read_unit()) && !output) {
			printf("RPM error, %08X\n", GetLastError());
			output = 1;
		}
		if (all_bombs == 2 | all_bombs == 3) {
			if (unit == last_unit) {
				if (last_all_bombs == 2 | last_all_bombs == 3) {
					if (current_bombs < last_current_bombs) {
						PostMessage(hWnd, WM_KEYDOWN, 0x00000053, 0x001F0001);
						PostMessage(hWnd, WM_CHAR, 0x00000073, 0x001F0001);
						PostMessage(hWnd, WM_KEYUP, 0x00000053, 0xC01F0001);
					}
				}
			}
		}
		last_unit = unit;
		last_all_bombs = all_bombs;
		last_current_bombs = current_bombs;
	}
}

 

 

Изменено пользователем Xipho
Куски кода более 10 строк у нас принято скрывать под спойлер
Ссылка на комментарий
Поделиться на другие сайты

С точки зрения чистоты кода, у тебя сейчас поток выполняет две разные задачи - выставляет флаг и потом, в зависомости от этого флага, действует. В целом, для небольшого функционала можно и так оставить. В твоем случае, чтобы проц не долбился в сотку, можно добавить sleep на 5-10 милисекунд, мне кажется, это должно решить твою проблему. Но если совсем по фен-шую делать - можно вынести функционал отправки клавиш и проверки режима в отдельный поток с бесконечным циклом и задержкой. А по клавишам поток создавать и убивать. Тогда и флаг не понадобится ) 

Ссылка на комментарий
Поделиться на другие сайты

5 часов назад, Xipho сказал:

С точки зрения чистоты кода, у тебя сейчас поток выполняет две разные задачи - выставляет флаг и потом, в зависомости от этого флага, действует. В целом, для небольшого функционала можно и так оставить. В твоем случае, чтобы проц не долбился в сотку, можно добавить sleep на 5-10 милисекунд, мне кажется, это должно решить твою проблему. Но если совсем по фен-шую делать - можно вынести функционал отправки клавиш и проверки режима в отдельный поток с бесконечным циклом и задержкой. А по клавишам поток создавать и убивать. Тогда и флаг не понадобится ) 

Спасибо. Собственно, то, о чем я и думал. А что насчет таймера вместо бесконечного цикла? Велика ли разница между ними в плане оптимизации?

Ссылка на комментарий
Поделиться на другие сайты

Это что, автоматическая перезарядка патронов? На что влияет отправка клавиш и что это за all_bombs?

Изменено пользователем Antonshka
Ссылка на комментарий
Поделиться на другие сайты

2 минуты назад, Antonshka сказал:

Это что, автоматическая перезарядка патронов?

Нет :)
Представь себе юнит - бомбардировщик. У него в боезапасе 3 бомбы. По умолчанию, при указании ему атаковать цель он очень быстро сбрасывает бомбы одну за другой, так что все бомбы будут сброшены практически сразу (между сбросом одной бомбы и последующей проходит 0.2 секунды). Однако, если вовремя нажать кнопку S, нажатие на которую эквивалентно отмене приказа для юнита (юнит должен перестать атаковать заданную цель), следующая бомба не будет сброшена, если она вообще у него есть.
То есть, допустим, бомбардировщик атакует цель изначально с 3 бомбами. Сбрасывает одну бомбу (остается 2), но кнопка S быстро нажимается и не дает ему сбрасывать дальше, допустив сброс только одной бомбы. Аналогично, затем он атакует цель уже с 2 бомбами, жмем S - остается 1 бомба. Конечно, далее останавливать его после сброса оставшейся одной бомбы - бессмысленно, но программа это тоже сделает и ничем не помешает.
Для своевременного нажатия кнопки S необходима отточенная реакция, что очень почитается на турнирах по данной игре. И эта программа, по моей задумке, должна автоматизировать реакцию человека.
Сброс по одной бомбе нередко нужен, потому что имеются цели, уничтожаемые менее, чем 3 бомбами. Рационально - экономичнее расходовать бомбы, так как пополнять боезапас можно только на аэродроме, до которого бомбардировщику нужно еще успеть вернуться.

Ссылка на комментарий
Поделиться на другие сайты

1 час назад, MaxSerro сказал:

А что насчет таймера вместо бесконечного цикла?

Исходя из твоей задачи (помним про 200 миллисекунд), таймер может не подойти, потому что у него не гарантировано срабатывание точно по его истечению. Таймер гарантирует срабатывание не раньше заданного интервала, но не регламентирует точность. 

 

1 час назад, MaxSerro сказал:

очень почитается на турнирах по данной игре

Смотри, как бы тебя не забанили за использование такой программы

 

 

Ссылка на комментарий
Поделиться на другие сайты

2 часа назад, Xipho сказал:

Смотри, как бы тебя не забанили за использование такой программы

 

 

Не забанят 😀
Я не играю в турниры, да и даже если бы играл, этот чит никак не отследить, потому что:
1) Сбрасывать по одной бомбе не запрещено. Если игра допускает такую возможность, почему это должно быть под запретом?
2) Чит никак бы не спалили, потому что моя программа не встраивает чужеродный код в игру, а лишь имитирует нажатие клавиш.

Изменено пользователем MaxSerro
Ссылка на комментарий
Поделиться на другие сайты

9 часов назад, MaxSerro сказал:

Чит никак бы не спалили, потому что моя программа не встраивает чужеродный код в игру, а лишь имитирует нажатие клавиш

Немного оффтопа - тут я вспомнил "войну" с одним покерным румом, для которого я как-то писал бота )) Вспомнил, и нервно захихикал от слов "никак бы не спалили... потому что... имитирует нажатие клавиш..." )) Впрочем, к онлайн играм, чаще всего, так строго не относятся ) 

Кстати, вместо отсылки сообщений можно использовать SendInput - у нее более широкие возможности
https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-sendinput
https://stackoverflow.com/a/22419083

Более широкие - например, можно имитировать сразу нажатие двух клавиш - сброс бомбы и отмену сброса следующей. Тогда, возможно, во втором потоке надобность отпадет.

  • Плюс 1
Ссылка на комментарий
Поделиться на другие сайты

10 часов назад, MaxSerro сказал:

2) Чит никак бы не спалили, потому что моя программа не встраивает чужеродный код в игру, а лишь имитирует нажатие клавиш.

У тебя чтение через OpenProcess и ReadProcessMemory? При желании их же может перехватить игра и использовать для определения.

 

13 часов назад, MaxSerro сказал:

Для своевременного нажатия кнопки S необходима отточенная реакция, что очень почитается на турнирах по данной игре.

Твоя программа при запуске создает второй поток, создает объект событие (с автосбросом), устанавливает SetWindowsHookExW на игру с флагом WH_KEYBOARD_LL (чтобы не было нужды в инжекте DLL).

Либо вместо SetWindowsHookExW, для большей гарантии от бана, использует Raw Input и флаг RIDEV_INPUTSINK.

Главный поток использует GetMessage, а не PeekMessage. Главный поток только активирует/деактивирует чит.

При нажатии клавиши сброса бомбы, твоя программа проверяет что  бомбы еще есть, и если есть, устанавливает событие в signaled. Поток пробуждается от сна (этот сон не тратит процессорное время), и начинает в цикле считывать значение. Если бомбы закончились, поток сам прерывает цикл считывание и возвращается на объект событие. Если бомбы еще есть но была нажата клавиша S, то поток также сам прерывает цикл и возвращается на объект событие. Определять была ли нажата клавиша S можно через тот же зарегистрированный Raw Input или через GetAsyncKeyState.

Такие мысли, чтобы уменьшить нагрузку на CPU.

 

  • Плюс 1
Ссылка на комментарий
Поделиться на другие сайты

6 часов назад, Xipho сказал:

Более широкие - например, можно имитировать сразу нажатие двух клавиш - сброс бомбы и отмену сброса следующей. Тогда, возможно, во втором потоке надобность отпадет.

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

Ссылка на комментарий
Поделиться на другие сайты

5 часов назад, Antonshka сказал:

У тебя чтение через OpenProcess и ReadProcessMemory? При желании их же может перехватить игра и использовать для определения.

Да, так и есть, все по методичке с канала "Михаил Ремизов", с видео, посвященным ESP-hack. Я ориентировался на код, предоставленный им, и подстроил под себя. А есть альтернативы им?

6 часов назад, Antonshka сказал:

Твоя программа при запуске создает второй поток, создает объект событие (с автосбросом), устанавливает SetWindowsHookExW на игру с флагом WH_KEYBOARD_LL (чтобы не было нужды в инжекте DLL).

Либо вместо SetWindowsHookExW, для большей гарантии от бана, использует Raw Input и флаг RIDEV_INPUTSINK.

Главный поток использует GetMessage, а не PeekMessage. Главный поток только активирует/деактивирует чит.

При нажатии клавиши сброса бомбы, твоя программа проверяет что  бомбы еще есть, и если есть, устанавливает событие в signaled. Поток пробуждается от сна (этот сон не тратит процессорное время), и начинает в цикле считывать значение. Если бомбы закончились, поток сам прерывает цикл считывание и возвращается на объект событие. Если бомбы еще есть но была нажата клавиша S, то поток также сам прерывает цикл и возвращается на объект событие. Определять была ли нажата клавиша S можно через тот же зарегистрированный Raw Input или через GetAsyncKeyState.

Такие мысли, чтобы уменьшить нагрузку на CPU.

 

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

Ссылка на комментарий
Поделиться на другие сайты

2 часа назад, MaxSerro сказал:

Да, так и есть, все по методичке с канала "Михаил Ремизов", с видео, посвященным ESP-hack. Я ориентировался на код, предоставленный им, и подстроил под себя. А есть альтернативы им?

Я не знаю. Я "зеленый", как и ты. @Xipho, скорее всего знает.

 

2 часа назад, MaxSerro сказал:

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

Для меня они тоже новые, я только вчера о них узнал.

Я себе недавно скачал книги, "Windows Internals Seventh Edition Part 1 Марк Руссинович", в ней про Kernel. И еще, "the windows 2000 device driver book", в ней про написание драйвера. Книга про Windows 2000, надо буде поискать посвежее. Но это у меня на будущее. Пока что они просто лежат.

 

Из моих размышлений, - чтобы игра смогла установить хук на OpenProcess или на ReadProcessMemory, она должна быть запущена в режиме Kernel, а не в User.  Либо она должна запустить свой собственный драйвер, для этой цели, для хука. И то, она хукает не эти функции, а те что с префиксом NT. Вызывая OpenProcess, мы в итоге вызываем NtOpenProcess.

По логике, чтобы без опаски использовать в своем приложении OpenProcess, нужно

- убедиться что игра не устанавливает хук на нее

- если же устанавливает, то тогда писать свой драйвер, который каким-то образом хукнет хукнутый OpenProcess, и твое приложение будет использовать хукнутый хук.  В обход хуку игры.

 

Это все мои фантазии и догадки. Нужно конечно же читать про все это. А я сейчас мыслями в GDI.

Ссылка на комментарий
Поделиться на другие сайты

23 часа назад, MaxSerro сказал:

По умолчанию, при указании ему атаковать цель он очень быстро сбрасывает бомбы одну за другой, так что все бомбы будут сброшены практически сразу (между сбросом одной бомбы и последующей проходит 0.2 секунды). Однако, если вовремя нажать кнопку S, нажатие на которую эквивалентно отмене приказа для юнита (юнит должен перестать атаковать заданную цель), следующая бомба не будет сброшена, если она вообще у него есть.

Исходя из твоей задачи, тебе не нужен весь этот огород, который ты мудришь. Достаточно простого макроса на мышь / клавиатуру (чем отдаешь ему указание атаковать цель).

  • Плюс 1
Ссылка на комментарий
Поделиться на другие сайты

6 минут назад, JustHack сказал:

Исходя из твоей задачи, тебе не нужен весь этот огород, который ты мудришь. Достаточно простого макроса на мышь / клавиатуру (чем отдаешь ему указание атаковать цель).

Не-не-не. Смотри. Я указываю бомбардировщику бомбить вражескую цель. ТОЛЬКО когда он сбросит одну бомбу (а это ему еще нужно долететь до цели), имитируется нажатие S. Не сразу после того, как я ему задал атаку, а именно после того, как был сброс! Соответственно, чтобы понять, был ли сброс, лезем в память.

Ссылка на комментарий
Поделиться на другие сайты

  • 3 месяца спустя...

@Xipho, привет снова!
Прошло уже довольно много времени с момента, как я обратился за помощью по улучшению кода на форум, но я не забывал о своей программе и улучшал ее как только мог.
Условимся, что сброс по одной бомбе - это 100%-ая эффективность программы, сброс по две бомбы (имитация нажатия отмены приказа клавишей S не поспевает) - это 50%-ая эффективность, а сброс по три бомбы - это 0%-ая эффективность, то есть никакая.
Программа сейчас идеально работает в одиночной игре, то есть со 100%-ой эффективностью, но при определенном диапазоне искусственной задержки, которую я поставил, чтобы процессор не был нагружен на 100%. Если точно, то я подобрал 1-40 мс задержки; если >40, то программа уже не поспевает. Напомню, что интервал времени между одной сброшенной бомбой и следующей составляет 200 мс.
В одиночной игре эффективность 100%, а вот в мультиплеере снижается до 50% (НИ РАЗУ не была 100%) и иногда до 0%. Уменьшение искусственной задержки до 1 мс ничего не дало, а 25 мс - оказалось слишком много. Я решил, что это нерешаемая проблема, так как соединение не идеально.
Я предполагаю, что механизм работы сетевой игры следующий: каждый клиент шлет в локальную сеть данные об изменениях в игровом процессе на своей клиентской стороне, например, перемещении юнита из точки А в точку Б - всем остальным клиентам; эти клиенты в случае успешного получения данных уведомляют об этом того клиента, что им прислал данные; уведомление приходит - и игровая ситуация меняется у всех. Топология сети - полносвязная: если лагает у одного, лагает у всех.
Так вот, у меня возникла мысль, а можно ли перехватить данные, которые клиент, управляющий бомбардировщиком, шлет остальным клиентам? Сейчас, скорее всего, картина следующая: я делаю запрос на сброс бомбы, данные о сбросе отсылаются всем клиентам, мне возвращается подтверждение на то, что сбрасывать можно, и бомба сбрасывается. Но только после того, как произошла запись в память моего клиентского компьютера, происходит имитация нажатия клавиши S. А ведь между моим запросом и подтверждением, а далее записью в память моего компьютера - проходит слишком много времени, чтобы моя программа успела отреагировать на сброс отменой приказа!
Я хочу перехватить данные еще до того, как они уйдут в локальную сеть и вернутся ко мне. Возможно ли это? Если да, есть ли у тебя соответствующее видео на эту тему? Я что-то находил, но там говорилось про перехват данных, полученных из локальной сети, а мне нужно перехватить то, что я шлю в локальную сеть. С помощью каких программ можно это сделать?
Думаю, если зафиксировать, что в одном сетевом пакете, описывающем прошлую игровую ситуацию на моей клиентской стороне, а в другом пакете по следующей ситуации - число бомб у выбранного бомбардировщика уменьшается с 3 до 2 или с 2 до 1 бомбы, то можно успеть остановить дальнейший сброс всех бомб.
UPD: Я уже видел уведомление о закрытии форума в начале 2023 и второпях написал этот комментарий по теме сразу же, как пришла мысль об улучшении работы программы по сети. Надеюсь, успеваю? :D

Ссылка на комментарий
Поделиться на другие сайты

Привет! 

 

54 минуты назад, MaxSerro сказал:

Я хочу перехватить данные еще до того, как они уйдут в локальную сеть и вернутся ко мне. Возможно ли это?

Да, это вполне возможно. Посмотри на канале видосы по расковыриванию Worms с помощью Ghidra, там где-то что-то было про перехват сетевых пакетов.

Ссылка на комментарий
Поделиться на другие сайты

58 минут назад, Xipho сказал:

Посмотри на канале видосы по расковыриванию Worms с помощью Ghidra, там где-то что-то было про перехват сетевых пакетов.

Возможно, ты имел в виду это видео: https://youtu.be/oV74F6i2cyA - и следующие за ним части

Изменено пользователем MaxSerro
Ссылка на комментарий
Поделиться на другие сайты

×
×
  • Создать...

Важная информация

Находясь на нашем сайте, Вы автоматически соглашаетесь соблюдать наши Условия использования.