M.1. Introdução à manipulação de memória
Enviado: 17 Ago 2018, 16:02
Tópico antigo: http://brmodstudio.forumeiros.com/t6565 ... de-memoria
Concluindo esta parte você vai:
Saber os princípios do manipulamento de memória, a sua utilidade e como usar os comandosREAD_MEMORY
eWRITE_MEMORY
Esta é uma parte fora dos "essenciais". É aqui que começamos a nos aprofundar na criação dos mods avançados!
Atenção:
Por favor, se você está aqui eu imagino que você já esteja começando a ficar bom na criação de mods cleos.
Este "episódio" onde eu ensino sobre memórias eu imagino que você já tenha lido todas as partes "Essenciais" do índice, senão você terá dificuldades.
Introdução
Os comandos
WRITE_MEMORY
e READ_MEMORY
foram os 2 primeiros comandos da biblioteca CLEO, ainda lá na CLEO 1.Ou seja, você está agora aprendendo algo que foi a origem de toda a CLEO.
E por que eles são tão especiais assim? Por que justamente estes dois comandos foram os primeiros de tantos? É agora que você entenderá.
Embaixo do capô
Modding é uma forma de hacking, e para fazermos estes hacks geralmente precisamos ir mais a fundo do que realmente podemos (poderíamos).
Com o manuseamento/manipulamento de memória nós podemos quebrar todos os limites que tínhamos, pegando/alterando qualquer valor, cada byte, cada bit do jogo inteiro, e até mesmo fora dele.
Isto serve principalmente para fazer coisas que nenhum comando existente faz. De fato, todo o funcionamento de um jogo (programa e seja o que for) funciona numa memória, como já foi levemente introduzido na parte Bits & Bytes do tutorial que você pode querer revê-la antes de prosseguir.
Tendo os endereços ("ponteiros", "pointers") para os locais destes valores na memória, nós podemos fazer o que bem entender, e estou falando de uma coisa que pode valer tanto para um endereço do próprio executável (que é estático) quanto para endereços dinâmicos, como o endereço onde está armazenado informações de um pedestre específico, como por exemplo, saber qual é o "skill" de arma dele (uma coisa que ainda não existe comando para isso, entendeu?)
Como usar os comandos?
Argumentos:
Código: Selecionar tudo
READ_MEMORY endereço tamanho virtual_protect (valor_retorno)
(INT) um ponteiro para algum local da memória, geralmente escrito em hexadecimal (0x).
tamanho
(INT) número de bytes para serem lidos, geralmente 1, 2 ou 4.
virtual_protect
(BOOL)* ative-o caso a memória tenha proteção. De fato, você só precisará caso fazer operações mais avançadas como hooks e NOPs, portanto, não se preocupe tanto com isso, normalmente você usará desativado.
* Lembrando que boolean pode ser usado como "0", "FALSE" ou "OFF" para desativado, e "1", "TRUE" ou "ON" para ativado.
valor_retorno
(QUALQUER) a variável que ganhará o valor que foi lido. Aceita qualquer data type, onde obviamente o compilador não sabe que valor irá retornar, portanto ele não te alertará de nada caso você estiver usando data type incorreto.
Exemplo:
Código: Selecionar tudo
READ_MEMORY 0xB7CE50 4 FALSE (var)
ponteiro onde está armazenado o dinheiro do player.
4
está armazenado em DWORD, onde uma DWORD contém 4 bytes, portanto, vamos ler 4 bytes desse endereço.
FALSE
é só uma simples leitura de um endereço comum, podemos usar FALSE (0) sem problemas.
var
variável onde será armazenado o valor contido neste endereço, onde neste caso em particular, é o dinheiro do player, e o dinheiro está armazenado em um INT (número inteiro), portanto vamos usar uma variável INT aqui.
Ou seja, agora a nossa variável chamada
var
tem o valor presente no endereço 0xB7CE50
, que é onde está guardado o dinheiro do player.Se você agora mostrar na tela:
Código: Selecionar tudo
PRINT_FORMATTED_NOW "%i" 1000 (var)
E se nós quisermos escrever um novo valor lá?
WRITE_MEMORY
!Argumentos:
Código: Selecionar tudo
WRITE_MEMORY endereço tamanho (valor) virtual_protect
Portanto...:
Código: Selecionar tudo
READ_MEMORY 0xB7CE50 4 FALSE (var)
var += 100
WRITE_MEMORY 0xB7CE50 4 (var) FALSE
100
, e em seguida re-escrevi o novo valor na memória, assim sobrescrevendo o valor anterior pelo meu novo valor. O que eu fiz foi simplesmente aumentar 100
no dinheiro do player.Outro exemplo:
Código: Selecionar tudo
WRITE_MEMORY 0xB7CE50 4 (1000) FALSE
1000
, simplesmente escrevendo o valor 1000
para onde o dinheiro dele está armazenado.Ei, mas isso é totalmente inútil, pois nós podemos usar os comandos
STORE_SCORE
e ADD_SCORE
para manipular o dinheiro do player!Você está certo meu caro questionador anônimo. Então que tal criarmos uma previsão do tempo? Sim, vamos prever o tempo melhor do que qualquer meteorologista:
Código: Selecionar tudo
READ_MEMORY 0xC8131C 2 FALSE (next_weather)
next_weather
agora tem o valor do próximo clima que está chegando (mais especificamente, um número de identificação do clima, se lhe interessou, dê uma olhada no source do meu mod Weather Forecast). E sim, você pode inclusive mudar este valor com o WRITE_MEMORY
.Mas possivelmente isso está te deixando mais confuso ainda: De onde diabos eu arranquei esse
0xB7CE50
e 0xC8131C
????? Como que eu sei que um é 4
e outro é 2
bytes??É aí que as coisas ficam mais interessantes!
Encontrando endereços para usar
Estes endereços são descobertos pela comunidade. Alguém conseguiu de alguma forma, geralmente tentando entender o arquivo .exe do jogo, usando um processo relativamente considerado ilegal chamado "engenharia reversa", usando IDA (Interactive Disassembler), e a tal pessoa soube que aquilo é o número de identificação do próximo clima, pois o número contido neste endereço de memória é usado dentro do .exe na função onde processa o próximo clima do jogo e assim por diante... (Ora ora, temos um Xeroque Rolmes aqui)
Você também pode encontrar seus próprios endereços de memória, mas eu não recomendo que você, neste momento, com baixa experiência, tente isso (principalmente caso não saiba os básicos do C++ e Assembly). Também porque no futuro eu tentarei criar um novo tutorial melhor aqui no fórum.
Ao invés, pesquise e encontre. Existe milhares de endereços já documentados que você pode usá-los, é só procurar no Google, você encontrará vários na GTA Forums por exemplo.
Eu recomendo altamente que você tente brincar com esta lista aqui:
http://www.gtamodding.com/wiki/Memory_Addresses_(SA)
Eu particularmente me encantei quando aprendi a manipular valores de endereços de memória e conheci esta lista!
Por exemplo:
Código: Selecionar tudo
0x863984 - [float] Gravity (default value: 1.0f/125.0f = 0.008f)
Código: Selecionar tudo
READ_MEMORY 0x863984 4 FALSE (gravity)
gravity
(que é uma FLOAT) agora terá o valor 0.008
, e você pode por exemplo aumentar, diminuir etc e reescrever o valor lá.Assim como... Que tal simular a gravidade de outros planetas/astros?
Código: Selecionar tudo
WRITE_MEMORY 0x863984 4 (0.00304) 0 // Mercúrio
WRITE_MEMORY 0x863984 4 (0.0072) 0 // Vênus
WRITE_MEMORY 0x863984 4 (0.00304) 0 // Marte
WRITE_MEMORY 0x863984 4 (0.021144) 0 // Júpiter
WRITE_MEMORY 0x863984 4 (0.009272) 0 // Saturno
WRITE_MEMORY 0x863984 4 (0.00936) 0 // Urano
WRITE_MEMORY 0x863984 4 (0.0088) 0 // Netuno
WRITE_MEMORY 0x863984 4 (0.000528) 0 // Plutão
WRITE_MEMORY 0x863984 4 (0.224) 0 // Sol
WRITE_MEMORY 0x863984 4 (0.00136) 0 // Lua
E você ainda pode estar confuso sobre a quantidade de bytes (argumento "size", o tamanho pra ser lido ou escrito), mas dá para simplificar:
Byte = 1 Byte é uma letra ou um número inteiro de valor 0 até 255 (ou -128 até 127 caso signed)
(2 bytes é até 65535, 4 bytes é até 2147483647)
Word = 2 Bytes
Dword = 4 Bytes
Qword = 8 Bytes — lembrando que nossas variáveis INT só cabem 4 bytes, e nem há variáveis de 8 bytes no jogo, exceto se for strings por exemplo.
Float = 4 Bytes
E isso é muito mais dinâmico que você possa imaginar.
Você pode ter o ponteiro de literalmente qualquer coisa (desde que você consiga, de alguma forma).
Por exemplo:
Código: Selecionar tudo
GET_VAR_POINTER var1 (pvar1)
pvar1
terá o ponteiro para a variável var1
.Se por exemplo a variável
var1
contém uma string (text label) com valor ABCDEF
e agora você fizer isto:Código: Selecionar tudo
pvar1 += 2
WRITE_MEMORY pvar1 1 (0x41) FALSE // 0x41(65) = "A"
var1
terá o valor ABADEF
, pois eu peguei o ponteiro para a (o início da) variável var1
, subi 2 bytes (ou seja, duas letras) e escrevi o valor hexadecimal 0x41
lá (que é o mesmo de decimal 65
), que é o hexadecimal para a letra "A" maiúscula. Com size de 1
byte, pois é só 1 letra.Sabendo que no fim de uma string contém um "null terminator" (você lembra da parte 9 do tutorial?) dizendo "fim da string", se nós escrevêssemos o valor
0
ali (que é o valor null, e quando eu digo "ali", me refiro a exatamente o mesmo código acima, ou seja, em vez de escrever A
, escrever um null terminator), a string da variável var1
se tornará somente AB
em vez de ABCDEF
(ou ABADEF
considerando o último exemplo). E isto aconteceu pois você colocou o null terminator (0
ou 0x0
ou 0x00
, são a mesma coisa) logo após o B
e assim agora a string só é considerada até lá, se tornando AB
. Interessante, não? Não que isso seja útil ao criar mods simples, mas ilustra muito bem o que temos aqui.Este é só um dos exemplos mais "avançados" e "dinâmicos" que você pode fazer, onde, de fato, você pode imaginar qualquer coisa, desde que entenda como os valores são armazenados numa memória (o que às vezes pode ser confuso, mas você vai aprendendo com o tempo usando muita lógica, teoria e prática).
Esta é só a primeira parte de uma parte mais avançada deste tutorial, e você ainda verá muito sobre endereços de memória, ponteiros, offsets etc nas próximas partes, o que lhe fará entender mais sobre o assunto.
Neste momento você pode treinar um pouco com a lista que já citei aqui, mesmo que não tenha tanta coisa, há bastante coisa útil.
Próxima parte:
Thread Memory