Исходник MVar (упрощенный доступ к переменным по оффсету)

Статус
В этой теме нельзя размещать новые ответы.

SR_team

like pancake
Автор темы
BH Team
4,716
6,357
Все хуйня, делайте как фип: 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<T>(var) - константное приведение переменной var к типу T. Преобразование типа будет производиться на этапе компиляции.
  • static_cast<T>(var) - приведение статичной переменной var к типу T. Преобразование типа, как и предыдущем случае, производится на этапе компиляции.
  • dynamic_cast<T>(var) - приведение динамической переменной var к типу T (тоже динамический). Преобразование типа производится в рантайме.
  • reinterpret_cast<T>(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<int*>(addr_money); // read
*static_cast<int*>(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 <windows.h>

template<class C>
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<C&>(*reinterpret_cast<C*>(addr()));
    }
    const C read()
    {
        return *reinterpret_cast<C*>(addr());
    }
    unsigned int addr()
    {
        return reinterpret_cast<unsigned int>(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<int> 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 курсача, так что буду тренироваться в разливание килотонн воды.
То вы лох конченый. Нахуй вам эта вода? У вас что, дома воды нет и вы решили, что можно помыться на форуме?
 
Последнее редактирование:
  • Нравится
Реакции: kiselqa, Breakaway и ishi

NarutoUA

NarutoUA
BH Team
692
1,537
Что за мазохизм то, этот класс будет плодить в бинарнике по 7 методов для каждого типа.
 

FYP

Известный
Администратор
1,757
5,692
жесть...
C++:
int& playerMoney = *(int*)0xB7CE50;
и можно обращаться как к обычной переменной - читать, писать. единственная проблема - нет снятия протекции.
 
Статус
В этой теме нельзя размещать новые ответы.