Показать сообщение отдельно

  #1  
Старый 04.06.2017, 22:47
SR_team
Флудер
Регистрация: 26.10.2013
Сообщений: 4,924
С нами: 6603505

Репутация: 183


По умолчанию

Все ***ня, делайте как фип: https://www.blast.hk/threads/16982/#post-147624

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

И так. "Что же это за хуета?" наверно возникла у вас мысль. Ответ можно дать в 9 словах (это точное число, я считал), но у меня дипломка в следующем году и 2 курсача, так что буду тренироваться в разливание килотонн воды. Иными словами текста будет неоправданно много и не нужно.

Если вы пишите или писали ранее плагины на C++ для GTA или какой-либо другой игры не имеющей для этих целей специализированного API, то вы наверняка сталкивались с тем, что вам нужно получить или записать значение по адресу в памяти. В принципе с такой задачей наверняка сталкивались даже те, кто пишет на CLEO.

Рассмотрим решение данной задачи на примере. Предположим, что мы хотим прочитать или записать количество денег в игре. Адрес по которому хранится количество денег в GTA:SA нам известен: 0xB7CE50.

Самый простой вариант чтения и записи значений в память выглядит следующим образом:

C:





Код:
int
var
=
*
(
int
*
)
0xB7CE50
;
// read
*
(
int
*
)
0xB7CE50
=
value
;
// write


Этот стиль записи пришел в C++ из языка программирования C, и на самом деле он рекомендуется к использованию. Если вы попытаетесь скомпилировать программу с таким кодом в компиляторе clang, то получите предупреждение о том, что так кодить не стоит. На замену такому стилю В C++ пришли следующие методы:
  • const_cast(var) - константное приведение переменной var к типу T. Преобразование типа будет производиться на этапе компиляции.
  • static_cast(var) - приведение статичной переменной var к типу T. Преобразование типа, как и предыдущем случае, производится на этапе компиляции.
  • dynamic_cast(var) - приведение динамической переменной var к типу T (тоже динамический). Преобразование типа производится в рантайме.
  • reinterpret_cast(var) - принудительное приведение переменной var к типу T. Никаких проверок на соответствие типу не производится.
Так же считается, что при использовании правильного для C++ приведения типов, указанного выше, программа компилируется быстрее, т.к. при использование приведения типов из языка программирования C, компилятор перебирает все возможные C++ приведения пока не найдет наиболее подходящее.

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

Если мы исследуем исходные коды плагина mod_s0beit, то мы можем найти записи, где непосредственно адрес переменной заменяется макросом, для нашего примера это будет такая запись:

C:





Код:
#define ADDR_MONEY 0xB7CE50


Тогда в коде вместо численного адреса можно использовать макрос. Это может иметь более длинную запись, однако читаемость кода выше:

C:





Код:
int
var
=
*
(
int
*
)
ADDR_MONEY
;
// read
*
(
int
*
)
ADDR_MONEY
=
value
;
// write


Как мы видим выше, даже не зная что хранится по адресу 0xB7CE50, программист читающий этот код сразу поймет что в нем делается. Однако тут мы находим еще один недостаток пришедший в C++ из языка программирования C - макросы. В C++ было принято соглашения, что в таких случаях следует использовать константы:

C++:





Код:
const
int
addr_money
=
0xB7CE50


Если мы применим все те методы C++, которые были описаны выше, то получится код вида:

C++:





Код:
int
var
=
*
static_cast

(
addr_money
)
;
// read
*
static_cast

(
addr_money
)
=
value
;
// write


Как мы видим это не самая красивая форма записи, и использование приемов из языка программирования C код выглядел более читабельным. К тому же, если обратиться к макросам, то можно записать такой макрос:

C:





Код:
#define MONEY *(int*)0xB7CE50


И тогда код будет выглядеть куда дружелюбнее к программисту:

C:





Код:
int
var
=
MONEY
;
// read
MONEY
=
value
;
// write


Такая запись почти идеальна. Почти... но тут используются приемы из языка программирования C. Однако не стоит расстраиваться, используя приемы C++ мы тоже можем добиться такой формы записи. Правда в C++ предполагается применение ООП и достичь такой же записи будет немного труднее - нам понадобится использовать классы и шаблоны. Но не беспокойтесь, я уже написал весь код, а вам только объясню как иего использовать.

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

C++:





Код:
#ifndef MVAR_H
#define MVAR_H
#include 
template

class
MVar
{
public
:
MVar
(
unsigned
int
offset
,
HMODULE module
=
0
)
:
offset
(
offset
)
,
module
(
module
)
{
hasFirstValueSaved
=
false
;
if
(
!
module
)
{
firstValue
=
read
(
)
;
// yeahhh....
defaultProtect
=
getProtection
(
)
;
}
}
const
C
getFirstValue
(
)
{
return
firstValue
;
}
void
restoreFirstValue
(
)
{
readLink
(
)
=
firstValue
;
}
unsigned
int
getProtection
(
)
{
DWORD protection
;
VirtualProtect
(
(
void
*
)
addr
(
)
,
sizeof
(
C
)
,
PAGE_EXECUTE_READWRITE
,
&
protection
)
;
VirtualProtect
(
(
void
*
)
addr
(
)
,
sizeof
(
C
)
,
protection
,
nullptr
)
;
return
protection
;
}
void
setProtection
(
unsigned
int
protect
)
{
VirtualProtect
(
(
void
*
)
addr
(
)
,
sizeof
(
C
)
,
protect
,
nullptr
)
;
}
void
unsetAllProtection
(
)
{
VirtualProtect
(
(
void
*
)
addr
(
)
,
sizeof
(
C
)
,
PAGE_EXECUTE_READWRITE
,
nullptr
)
;
}
void
restoreProtection
(
)
{
VirtualProtect
(
(
void
*
)
addr
(
)
,
sizeof
(
C
)
,
defaultProtect
,
nullptr
)
;
}
C
&
operator
(
)
(
)
{
if
(
!
hasFirstValueSaved
)
{
firstValue
=
read
(
)
;
hasFirstValueSaved
=
true
;
}
return
readLink
(
)
;
}
protected
:
C
&
readLink
(
)
{
return
static_cast

(
*
reinterpret_cast

(
addr
(
)
)
)
;
}
const
C
read
(
)
{
return
*
reinterpret_cast

(
addr
(
)
)
;
}
unsigned
int
addr
(
)
{
return
reinterpret_cast

(
module
)
+
offset
;
}
private
:
unsigned
int
offset
;
HMODULE module
;
C firstValue
;
unsigned
int
defaultProtect
;
bool
hasFirstValueSaved
;
}
;
#endif
// MVAR_H


Код позиционируется кк самостоятельный .h файл подключаемый к проекту. Как вы видите, не смотря на желание уйти от макросов, мне это не удалось. Дело в том, что в C++ не стандартизированного метода указания, что файл должен быть включен лишь единожды. В Microsoft VisualStudio C++ есть #pragma once, однако другие компиляторы его не поддерживают.

Начнем с конструктора. Он обязательно принимает смещение в памяти и не обязательно может принимать указатель на модуль в котором располагается наша переменная.

C++:





Код:
MVar

money
(
0xB7CE50
)
;


Из кода выше видно, что мы создали объект money, которому передали адрес денег в памяти игры. Конструктор не может быть объявлен глобально! Т.к. я использую свой шаблон для asi плагинов, который построен на классах, то у меня с этим нет проблем, однако если вы пишите плагин в стиле языка программирования C (например SF плагин), то у вас могут возникнуть с этим некоторые проблемы.

Пример чтения и записи:

C++:





Код:
int
var
=
money
(
)
;
// read
money
(
)
=
value
;
// write


Теперь рассмотрим остальные, менее интересные методы:
  • getFirstValue() - возвращает значение, которое было до создания объекта класса.
  • restoreFirstValue() - запись значения, которое было до создания объекта класса.
  • getProtection() - возвращает текущие разрешения на операции с памятью.
  • setProtection(protect) - устанавливает указанные вами разрешения на операции с памятью.
  • unsetAllProtection() - снимает все ограничения на операции с памятью.
  • restoreProtection() - восстанавливает разрешения на операции с памятью, которые были до создания объекта класса.

P.S. Если вы не пролистали сразу вниз после прочтения фразы:

Цитата:

Ответ можно дать в 9 словах (это точное число, я считал), но у меня дипломка в следующем году и 2 курсача, так что буду тренироваться в разливание килотонн воды.
То вы лох конченый. На*** вам эта вода? У вас что, дома воды нет и вы решили, что можно помыться на форуме?
 
Ответить с цитированием