[Tutorial] Como ler um BIT de um BYTE
Enviado: 01 Dez 2021, 14:46
Greetings from Vice City!
Enquanto eu trabalhava no meu projeto de criação de um mod para 2 jogadores do Vice City, me deparei com a necessidade de ler um único bit de um determinado byte.

Para implementar um sistema de pulo, eu precisava saber se o personagem estava no ar (obviamente ele não poderia pular se estivesse). Deveria ser bastante simples fazer isso, mas o GTA3Script parece não ter sido preparado para esse tipo de operação, haja vista que o OPCODE de leitura de memória (MEMORY_READ) tem como argumento o BYTE e não o BIT, ou seja, ele só faz leitura de múltiplos de 8 bits (8, 16, 24, 32, etc...).
Mesmo assim, eu pensei que seria bem simples de recuperar aquele bendito bit pois, como sabemos, cada byte é composto de 8 bits, numa estrutura assim:
Mesmo assim, eu pensei que seria bem simples de recuperar aquele bendito bit pois, como sabemos, cada byte é composto de 8 bits, numa estrutura assim:

Em outras palavras, o número daí de cima, em decimal, é 45, pois consiste na soma de cada parte dele (32+8+4+1).
Então agora ficou fácil! Já que o terceiro bit equivale a 8 (devemos lembrar que a contagem começa em 0), basta que eu verifique se meu byte equivale a 8 (0000 1000), correto?
Não! (necessariamente)
Isso só seria verdade se o único bit alterado nesse byte fosse o terceiro, enquanto todos os outros permanecessem estáticos. No mundo real, normalmente isso não acontece. Mesmo que acontecesse, eu não poderia me dar ao luxo de confiar cegamente assim. No vídeo abaixo, no qual eu monitoro o valor do byte (em decimal) enquanto o mod roda, podemos ver que ele assume diversos valores:
Então agora ficou fácil! Já que o terceiro bit equivale a 8 (devemos lembrar que a contagem começa em 0), basta que eu verifique se meu byte equivale a 8 (0000 1000), correto?
Não! (necessariamente)
Isso só seria verdade se o único bit alterado nesse byte fosse o terceiro, enquanto todos os outros permanecessem estáticos. No mundo real, normalmente isso não acontece. Mesmo que acontecesse, eu não poderia me dar ao luxo de confiar cegamente assim. No vídeo abaixo, no qual eu monitoro o valor do byte (em decimal) enquanto o mod roda, podemos ver que ele assume diversos valores:
Percebam que ele começa com 0 (0000 0000), depois vai para 32 (0010 0000), então 40 (0010 1000), 48 (0011 0000)... Ou seja, eu tenho diversos valores para o byte ao longo do tempo. Mas você pode pensar: "aaaahh, então basta eu pegar os valores cujo o terceiro bit seja 1 e verificar". Essa poderia ser uma saída, mas um pouco de análise combinatória, nos responderia que existem 128 valores possíveis para isso! (8 (0000 1000), 9 (0000 1001), 10 (0000 1010), 11 (0000 1011), 12 (0000 1100)...), o que torna impraticável aplicar isso em um código. A quantidade de IF seria muito grande! (neste caso 16 IF com 8 condições CADA)
Por outro lado, podemos perceber que o meu terceiro bit está funcionando como esperado pois ele só retorna 1 quando o personagem está de fato no ar. No vídeo, o byte assume o valor 40 (0010 1000), 42 (0010 1010)... Ou seja, a documentação tá correta e vai me ajudar a saber quando o personagem está no ar.
Mas voltando: então, como ler esse bit? Eu perguntei ao pessoal no grupo do discord, tanto na seção em português quanto em inglês e ambos me sugeriram utilizar opcodes somente presentes no CLEO do GTA San Andreas. Como estou codificando para o Vice City, obviamente não daria para fazer isso.
Vasculhando um pouco mais o GTA3Script, encontrei dois opcodes interessantes: BIT_SHL e BIT_SHR
Mas afinal, o que seriam eles?
Um pouco de Google ajuda e logo de cara eu descobri que servem para deslocar os bits de um byte para a esquerda ou para a direita (agora o L e o R ali fazem sentido?)
Por outro lado, podemos perceber que o meu terceiro bit está funcionando como esperado pois ele só retorna 1 quando o personagem está de fato no ar. No vídeo, o byte assume o valor 40 (0010 1000), 42 (0010 1010)... Ou seja, a documentação tá correta e vai me ajudar a saber quando o personagem está no ar.
Mas voltando: então, como ler esse bit? Eu perguntei ao pessoal no grupo do discord, tanto na seção em português quanto em inglês e ambos me sugeriram utilizar opcodes somente presentes no CLEO do GTA San Andreas. Como estou codificando para o Vice City, obviamente não daria para fazer isso.
Vasculhando um pouco mais o GTA3Script, encontrei dois opcodes interessantes: BIT_SHL e BIT_SHR
Mas afinal, o que seriam eles?
Um pouco de Google ajuda e logo de cara eu descobri que servem para deslocar os bits de um byte para a esquerda ou para a direita (agora o L e o R ali fazem sentido?)

Se eu tiver um 6 (0000 0110) e aplicar um SHR de 2 (mover os bits 2 vezes para a direita), em teoria, ele se torna um 1 (0000 0001) (os números à direita, quando deslocados, se perdem se não houver "espaço").
OK, então agora bastaria que eu deslocasse meu querido byte em 4 para a esquerda, assim bastaria verificar se o resultado fosse maior que 127, afinal mesmo que todos os outros bytes, agora à direita do meu número, fossem 1, o valor máximo que eu conseguiria seria 127 (0111 1111), certo?
Não novamente! (caramba isso tá virando uma odisseia, né? mas modding não é isso? ficar testando as coisas...)
Acontece que (e eu não sei explicar por que devido ao meu conhecimento limitado terminei descobrindo) os valores anteriormente à esquerda vão mais para a esquerda! E os valores mostrados crescem, como por exemplo 640 (0010 1000 0000) (talvez estejam vazando para o byte anterior? Não é isso. Analisando um pouco melhor, me lembrei que o int (que é o que usamos para guardar o valor do byte) possui 4 bytes. Ou seja, teríamos que empurrar MUITO para a esquerda (mais precisamente, 60) para que os bits à esquerda desapareçam do nosso DWORD (4 bytes). A variável int é muito grande, se escrita em binário (0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000). Mas nada disso importa agora, visto que antes de perceber essa questão, encontrei uma outra maneira melhor e mais prática para ler o valor do bit)
Visto que isso não poderia me ajudar (até então), decidi buscar informações sobre os outros opcodes também relacionado a bits e que poderiam talvez possuir minha resposta. Um deles era o BIT_AND, que possuía 2 int de entrada e 1 de saída. Opa, parece que temos uma luz no fim do túnel... maaass como exatamente esse BIT_AND funciona??
Voltamos pro Google e infelizmente "BIT" e "AND" são palavras extremamente comuns para se pesquisar, principalmente em se tratando de programação. Opcode database? Não tem nada que ajude de fato.
Então, como resolver? Testando!
Eu criei um código simples de debug (abaixo) para tentar entender o que esse OPCODE faz. A ideia era imputar diversos números e tentar interpretar os resultados.
OK, então agora bastaria que eu deslocasse meu querido byte em 4 para a esquerda, assim bastaria verificar se o resultado fosse maior que 127, afinal mesmo que todos os outros bytes, agora à direita do meu número, fossem 1, o valor máximo que eu conseguiria seria 127 (0111 1111), certo?
Não novamente! (caramba isso tá virando uma odisseia, né? mas modding não é isso? ficar testando as coisas...)
Acontece que (e eu não sei explicar por que devido ao meu conhecimento limitado terminei descobrindo) os valores anteriormente à esquerda vão mais para a esquerda! E os valores mostrados crescem, como por exemplo 640 (0010 1000 0000) (talvez estejam vazando para o byte anterior? Não é isso. Analisando um pouco melhor, me lembrei que o int (que é o que usamos para guardar o valor do byte) possui 4 bytes. Ou seja, teríamos que empurrar MUITO para a esquerda (mais precisamente, 60) para que os bits à esquerda desapareçam do nosso DWORD (4 bytes). A variável int é muito grande, se escrita em binário (0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000). Mas nada disso importa agora, visto que antes de perceber essa questão, encontrei uma outra maneira melhor e mais prática para ler o valor do bit)
Visto que isso não poderia me ajudar (até então), decidi buscar informações sobre os outros opcodes também relacionado a bits e que poderiam talvez possuir minha resposta. Um deles era o BIT_AND, que possuía 2 int de entrada e 1 de saída. Opa, parece que temos uma luz no fim do túnel... maaass como exatamente esse BIT_AND funciona??

Voltamos pro Google e infelizmente "BIT" e "AND" são palavras extremamente comuns para se pesquisar, principalmente em se tratando de programação. Opcode database? Não tem nada que ajude de fato.
Então, como resolver? Testando!
Eu criei um código simples de debug (abaixo) para tentar entender o que esse OPCODE faz. A ideia era imputar diversos números e tentar interpretar os resultados.
SpoilerAbrir
Código: Selecionar tudo
SCRIPT_START
{
LVAR_INT a b res
a = 0
b = 0
main:
IF IS_KEY_PRESSED VK_KEY_A
a += 1
WHILE IS_KEY_PRESSED VK_KEY_A
WAIT 0
ENDWHILE
ENDIF
IF IS_KEY_PRESSED VK_KEY_B
b += 1
WHILE IS_KEY_PRESSED VK_KEY_B
WAIT 0
ENDWHILE
ENDIF
BIT_AND a b res
PRINT_FORMATTED_NOW "BIT_AND a: %i b: %i = %i" 1000 a b res
WAIT 0
GOTO main
}
SCRIPT_ENDO par 1 e 2 me retornava 0.
2 e 2 retornava 2.
3 e 2 = 2.
5 e 2? 0.
hum, tá começando a ficar interessante...
Para quem ainda não percebeu, basicamente o que esse opcode está fazendo é operar AND em cada bit dos números. Ou seja, no caso de 1 (0001) e 2 (0010), o resultado é (0000). No caso de 3 (0011) e 2 (0010) o resultado é 2 (0010) (só o bit 1 faz o AND retornar 1). E assim para todo o resto...
O que aconteceria se eu operasse qualquer que fosse o int com o número 8 (0000 1000)? Exato! Ele só iria me retornar zeros ou oitos! Ou seja, acabei de encontrar uma maneira simples e prática de ler se o terceiro bit de um byte está ativo. Se ele estiver, vai me retornar um 8. Caso não, um zero.
2 e 2 retornava 2.
3 e 2 = 2.
5 e 2? 0.
hum, tá começando a ficar interessante...
Para quem ainda não percebeu, basicamente o que esse opcode está fazendo é operar AND em cada bit dos números. Ou seja, no caso de 1 (0001) e 2 (0010), o resultado é (0000). No caso de 3 (0011) e 2 (0010) o resultado é 2 (0010) (só o bit 1 faz o AND retornar 1). E assim para todo o resto...
O que aconteceria se eu operasse qualquer que fosse o int com o número 8 (0000 1000)? Exato! Ele só iria me retornar zeros ou oitos! Ou seja, acabei de encontrar uma maneira simples e prática de ler se o terceiro bit de um byte está ativo. Se ele estiver, vai me retornar um 8. Caso não, um zero.
Código: Selecionar tudo
[...]
GET_PED_POINTER scplayer2 memtemp
memtemp += 0x14D //offset do CPed do byte que diz se está "nos céus" (airborne)
READ_MEMORY memtemp 1 0 var //optei por outra variável mas o próprio memtemp poderia receber o resultado
BIT_AND var 8 res //verificando se o terceiro bit está ativo
IF res = 8
PRINT_FORMATTED_NOW "O boneco ta voando!"
ELSE
PRINT_FORMATTED_NOW "O boneco ta com os pes no chao!"
ENDIF
[...]Isso vale para qualquer que seja o bit de um determinado byte. Se eu quisesse ler o 5º bit de um byte? Bastaria operar um BIT_AND da minha variável com 32 (0010 0000). E assim vai, acho que já deu pra entender, né?
E ainda podemos transformar isso em binário (caso vocês queiram, a meu ver, não parece ser tão útil), bastando que peguemos o nosso resultado e o desloque em 3 para a direita.
E ainda podemos transformar isso em binário (caso vocês queiram, a meu ver, não parece ser tão útil), bastando que peguemos o nosso resultado e o desloque em 3 para a direita.
Código: Selecionar tudo
[...]
BIT_SHR var 3 res //res agora possui valor 0 ou 1
[...]
Bem, é isso. A jornada de aprender a modificar as coisas é um pouco estressante e às vezes frustrante, mas demasiadamente gratificante.
Obrigado por me acompanhar até aqui e até o próximo tutorial! (se houver, né
)
Obrigado por me acompanhar até aqui e até o próximo tutorial! (se houver, né
)



