Engenharia reversa no GBA: Bora pintar o nosso site — Parte 7
Essa postagem faz parte da série intitulada Engenharia Reversa num jogo de Gameboy Advance. Leia a introdução aqui. Leia a postagem anterior aqui.
Me siga no Twitter para acompanhar mais computarias 🐦
Mergulhamos pra caralho na engenharia reversa e só petiscamos um pouquinho no JS a partir dos últimos capítulos… mas essa postagem será completamente diferente: vamos descer a ladeira no desenvolvimento web, assim aplicando em nosso editor de fases tudo o que descobrimos.
A seguir eu pontuarei as decisões mais relevantes na programação do klo-gba.js, justificando o porque de ter sido assim, o que obtive, e o que aprendi com isso. Quero nada de "num sei, só sei que foi assim" aqui.
Diferente das demais postagens, em que eu contava o storytelling cronologicamente, não farei o mesmo aqui. Em cada seção descreverei uma decisão de design, da mais macro para a mais micro.
Um disclaimer: o klo-gba.js é um projeto pessoal, ou seja, desenvolvo no meu tempo livre e por puro hobby! Então algumas das decisões e justificativas são influenciadas pelo hobbismo!
Web App?
Desde a introdução eu falei de desenvolver um web app… Mas você chegou a se questionar de porque estou falando de web? Em geral, essas ferramentas de engenharia reversa são desktop. O próprio no$gba é desktop. Os editores de fase para os jogos de GBA do Pokémon também são desktop. Os emuladores mais populares de Switch e do 3DS são desktop. Até mesmo esse simples programa para verificar a integridade dos arquivos do Swtich também é desktop.
Por qual razão estamos fazendo diferente?
Bem, a principal razão de porque estou fazendo um web app é bem simples: eu sou apaixonado pela web. Tal como diz o Gabriel Guimarães, um dos engineering manager da unicórnio Brex, "a internet é um delivery de código": o usuário só precisa acessar uma URL que já consegue usufruir de um serviço. Trata-se de um ambiente aberto onde todos podem conviver e aplicar suas tecnologias.
Seria um completo desperdício fazermos uma aplicação desktop quando podemos prover tudo a partir de uma simples URL. Assim não obrigamos o usuário a ter que baixar um software só para brincar um pouco em nosso editor de mapas. Isso inclusive viabiliza que mais usuários iterem em versões alfas do editor.
Os últimos avanços viabilizam cada vez mais que seja desenvolvido verdadeiras aplicações web — seria realmente difícil de se fazer essa nossa aplicação a 10 anos atrás, por exemplo. O WebGL e bibliotecas como React facilitam bastante e assim torna tudo isso possível.
JS?
Okay. Vamos fazer um web app… mas… escrevendo em JS? Há tantas coisas melhores em pleno 2019… Linguagens como TypeScript, ClojureScript, ReasonML…
Bem, nesse quesito devo que admitir: JS foi uma péssima escolha e me arrependo.
Escolhi desenvolver em JS apenas porque já estava mais habituado, além de que bibliotecas que pretendia usar funcionam melhor com JS, tais como a lib de funcional Ramda. Outra lib de exemplo é a de front que já havia decidido a utilizar, Former-Kit, da qual também não é tipada, o que diminui os ganhos em usar linguagens como TS.
Outra razão que me fez optar por JS é que estava na esperança que, usando uma linguagem mais popular, atrairia mais pessoas para eventualmente colaborar no projeto. Bem, até recebi alguns PRs de algumas pessoas, mas foram bem poucos, o que não compensa o investimento de usar JS.
Mesmo tratando-se de um simples projeto pessoal, frequentemente cometo erros que linguagens com tipagem estática, TS e ReasonML, poderiam evitar (o que é mesmo que esse objeto pode receber? quais os possíveis estados possíveis desse enum?).
Uma curiosidade é que, por ser tratar de um projeto completamente pessoal, tenho bem mais liberdade de experimentar diferentes ideias e tecnologias. Uma delas foi experimentar o plugin do babel de pipe-operator. Assim torna o código JS um pouquinho mais tragável.
Esse plugin trabalha muito bem com o Ramda, pois todas as funções do Ramda são curry. Veremos um pequeno exemplo disso logo mais.
Aplicação 100% estática
Desde o começo do desenvolvimento eu tinha a visão de querer desenvolver uma aplicação 100% estática. Eu já tive sofridas experiências com o meu blog pessoal, da qual na primeira versão dele eu tinha que manter um servidor, e ele só dava problema de manutenção. Quando mudei para usar somente GitHub Pages e terceirizei partes que precisavam de um servidor (como nos comentários), nunca mais tive que me preocupar com isso. It just works.
Afinal, pra que um servidor? Todas as nossas operações que precisaremos (carregar a ROM, pintar o mapa, editá-lo e salvar a ROM customizada) podem ser feitas diretamente pelo cliente. Ou seja: nós não precisamos de um servidor. Então pra que ter um? Seria apenas uma abstração a mais para se preocupar a da manutenção!
Por exemplo, teríamos que se preocupar mais com segurança, em fazer a comunicação do client com o servidor… Enfim, não ter um servidor é benéfico para essa aplicação!
Como já usaria o GitHub para armazenar o meu código, e já tive boas experiências com o GitHub Pages, decidi que também o usaria para hospedar o site. Claro, poderia usar qualquer outra coisa, como o Now, que também já tive boas experiências.
Fazer o deploy para o GitHub Pages foi realmente trivial, pois existe um pacote no NPM que faz tudo para mim, o gh-pages
. Esse é o PR que implementa isso (dica: ao estudar os meus PRs, veja commit por commit! Eu sempre busco criar commit atômicos, e peço que todos façam o mesmo).
React
Eu desenvolvo profissionalmente com React a um certo tempo, e gosto muito dele. A forma de pensar no front como sendo um conjunto de componentes, com cada qual tendo um conjunto possíveis de estados, e escrever um código declarativo na UI, sempre me fez muito sentido.
Por isso não tive muita dificuldade em escolher o React. Um pequeno spoiler: mais para frente veremos que nem sempre é bom usar React em determinadas partes do front.
Como lib de componente, escolhi o Former-Kit. Essa é a lib de componentes de uma empresa que já trabalhei, e é inteiramente open source. Usá-la no projeto me da bastante agilidade (pois já tenho experiência nela e meus amigos que a desenvolvem podem me ajudar) e flexibilidade (pois eu sei como mexer dentro dessa biblioteca para customiza-la e abrir PRs, o que aconteceu algumas vezes no decorrer do projeto).
Além disso, essa biblioteca trabalha muito bem com UI baseada em cartões, e me faz muito sentido desenhar a interface do klo-gba.js como sendo composta por vários cartões. Um cartão para exibir o tilemap, outro cartão para exibir detalhes de um objeto…
Por fim, há também uma razão mais pessoal: colaborar no Former-Kit, desenvolvendo-o e divulgando-o, me deixa feliz e mais motivado a programar, pois eu tenho um carinho especial pelos meus amigos que a desenvolvem.
Monorepo
Um ponto interessante no projeto é a arquitetura de monorepo. Ou seja, um único repositório que armazena mais que um projeto. Essa é uma boa forma de ter uma divisão mais clara das responsabilidade entre as partes de uma aplicação, pois a divisão fica explícita no código.
Mas como assim dois projetos? Não estamos fazendo só um único web app? Calma… Vai ficar mais claro logo mais.
O klo-gba.js inicialmente foi composto por apenas dois projetos: 🖌 brush e ✂️ scissors. O primeiro é o responsável por toda a parte de UI, ou seja, com o que o usuário interage diretamente no browser, com o que o usuário visualiza. Já o segundo seria o "back-end", ou seja, é o responsável por manipular os dados da ROM, armazenar as informações das fases…
Por exemplo, o brush é o que mapeia qual é a cor de cada tile, enquanto o scissors é o que mapeia o id de um tile para o tipo dele. O scissors nem sabe o que é React, enquanto o brush nem sabe como manipular o buffer da ROM.
O scissors armazena arquivos json-like com os dados de cada fase (como o endereço do tilemap, o tamanho da fase, etc). O scissors trata esses dados para responder o que o brush pede.
Fazer com monorepo, ao invés de dois repositórios separados, ajuda a ter um nível saudável de encapsulamento. Por exemplo, isso facilita quando for abrir um PR ou analisar o histórico de commits do repositório — o que da um ganho de agilidade. Se um PR afetar ambos projetos, não precisa abrir dois PR em repositórios separados, mas apenas um único PR que edita ambos.
A medida que a aplicação for crescendo, nós podemos ter mais projetos. Como por exemplo, se formos desenvolver um novo e complexo componente de UI, ao invés de desenvolvê-lo dentro do brush, podemos desenvolve-lo como sendo um projeto a parte e importá-lo como dependência no brush. Ou então podemos desacoplar um componente já existente no brush criando um novo projeto dentro do monorepo.
Isso de fato foi algo que aconteceu após vários meses de desenvolvimento. Quando decidi adicionar um emulador do GBA dentro da página, criei um novo projeto dentro do monorepo, o 🕹 react-gbajs, o que ajudou a reduzir a complexidade do brush.
Um outro cenário é que desejo desacoplar do brush o componente Tilemap
, de forma com que seja um componente React isolado, para assim disponibiliza-lo para a comunidade. Porém, um passo intermediário para chegar a esse resultado seria criá-lo como um novo projeto dentro do klo-gba.js, e só quando enfim o componente do Tilemap
estiver estável ele seria movido para um novo repositório. Esse passo intermediário seria bom para ganhar agilidade no desenvolvimento.
Ah sim, e para não perder o histórico de commits quando for mover para o novo repositório pode-se usar o comando git subtree
.
Uma desvantagem de monorepo é que aumenta a complexidade do processo de build, pois temos agora que compilar dois projetos e linká-los "de algum modo". Existem algumas formas diferentes de se fazer isso. A que apliquei inicialmente no klo-gba.js é uma das mais simples possíveis: apenas importei o scissors dentro do brush com o caminho relativo do diretório dele:
"dependencies": {
...
"scissors": "file:../scissors"
},
No outro extremo de complexidade, uma outra abordagem para monorepo com diversos projetos é usar o Lerna, que é uma ferramenta para gerenciar as dependências entre os projetos de um monorepo, e inclusive gerenciar também as dependências dentro de cada projeto.
Uma das coisas que ele faz é, por exemplo, se vários projetos precisam exatamente da mesma dependência, ao invés de baixa-la múltiplas vezes, baixa uma única vez e cria um link simbólico para ela.
Claramente, como o klo-gba.js é bem simples, não vi necessidade de usar o Lerna.
Quando eram apenas dois projeto, usar NPM linkando a dependência com file:
era satisfatório, porém, quando fui adicionar o terceiro projeto dentro do monorepo, visando facilitar e acelerar a instalação das dependências, migrei para o uso do Yarn's Workspaces, mudança que foi realizada nesse PR.
A dashboard de uma empresa que trabalhei também usam a abordagem de monorepo por meio do Yarn's Workspace. Como ela é open source você pode estudar o código dela aqui.
Tilemap
O primeiro componente a ser desenvolvido foi o Tilemap
, antes mesmo do klo-gba.js ter nome. Ele é, sem dúvida alguma, o componente mais complexo de toda a aplicação. Curiosamente, ele representa cerca de ~35% da codebase do brush. Esse componente tem duas grandes responsabilidades: plotar o tilemap com os objetos & permitir que o usuário o customize.
Lembra daquelas POCs que desenvolvemos, para plotar a fase num arquivo de imagem? Então, a partir dela comecei a pensar em como desenvolver o Tilemap
. A visão mais clara que tinha, a partir do código que desenhava um BMP, era simplesmente plotar no browser com canvas, e assim comecei a desenvolver. Para tal, escolhi usar a lib konva
e o wrapper react-konva
.
Desse modo, fiz com que cada tile fosse um ponto no canvas, e cada ponto era um componente React. Isso facilita bastante para implementar as interações com o mouse.
E assim funcionou! Porém… ficou extremamente pouco performático, pois tínhamos que renderizar muitos componentes. Só para ter uma ideia, a segunda fase tem 18.000 tiles, ou sejam, são criados cerca de 18.000 componentes React!
A primeira renderização da fase demandava ~3 segundos, e se for trocar de fase demandava ~13 segundos!! Esse tempo mais acentuado na troca de fase provavelmente devia acontecer porque o React tenta aproveita componentes já presentes na tela, porém, como se tratava de uma completa troca de fase, todas as tentativas de reutilizar os tiles falharia, e assim levava bem mais tempo para atualizar o virtual DOM e, posteriormente, pintar na tela.
Tal como dito, além de renderizar a fase também é necessário atualizar a cor de um tile ao modificá-lo, e obviamente não seria agradável ter que esperar vários segundos para cada clique no mapa. Assim, eu fiz um código extremamente bizarro usando refs para evitar o re-render do Tilemap
ao trocar a cor de um ponto. Porém, ainda assim continuava levando muitos segundos para renderizar o tilemap.
Então continuei a buscar uma forma de otimizar, e me chamou atenção esse algoritmo. Se você ver a imagem abaixo já vai pegar a ideia. Com esse algoritmo podemos ter menos componentes na tela e, com menos componentes na tela, conseguiremos renderizar mais rapidamente!
Fazer a implementação desse algoritmo não foi tão trivial, não só pelo desafio de buscar "crescer o componente até o melhor tamanho possível", mas também porque aumentava a complexidade ao pintar um tile. Aqui está o PR que o implementa uma versão simplificada desse algoritmo. Após essa otimização, consegui reduzir para 1.8s o primeiro render e 3.6s os renders seguintes. Apesar de ainda estar meio devagar, já é um bom avanço, não? Porém, o código ficou bem mais complexo…
Visando obter feedbacks e ir me preparando para apresentar o projeto numa conferência, apresentei pela primeira vez o klo-gba.js no meetup ReactSP#35. Dediquei uma das seções da talk para abordar a complexidade que é o funcionamento do Tilemap
, e o pessoal me recomendou fortemente a abandonar Canvas e passar a usar WebGL.
Okay. Escutando esses feedbacks, decidi refatorar tudo para usar WebGL, dessa vez usando a lib @inlet/react-pixi
, da qual usa de baixo dos panos a pixi.js
. Só simplesmente trocar de Canvas para WebGL, e ainda mantendo a mesma arquitetura, já obtive um ganho de perfomance. O primeiro render passou a demandar 1.6s e os subsequentes 2.8s! Esse é o PR com a troca.
Então, como próximo passo, ainda com base nos feedbacks, decidi diminuir a quantidade absurda de componentes na tela. Ao invés de cada tile ser um componente, decidi renderizar um único componente React e, dentro dele, mandar o WebGL pintar os tiles. Com isso, foi possível obter um código muito mais legível e otimizado! O primeiro render passou a demandar míseros 0.625s e os subsequentes 0.536s. Esse é o PR com a implementação.
Tilemap
Claro, se eu tivesse começado desde cedo a usar WebGL, eu apanharia bem menos para esse componente, porém, foi a primeira vez que realmente desenvolvi alguma coisa usando essas tecnologias. Foi todo um processo de aprendizado e descoberta.
Ademais, espero no futuro ter tempo para poder desacoplar todo o Tilemap
num componente React a parte, pois vejo que ele pode ser útil para outros projetos da comunidade.
Provider Pattern
Algo muito popular em desenvolvimento com React é usar "Redux-alguma-coisa" para gerenciar o estado global da aplicação. Okay, estava desenvolvendo um novo projeto do zero, porém, fiquei com uma certa dúvida se precisaria ou não aplicar Redux.
Discutindo com pessoas mais experientes no front, chegamos a conclusão que não precisamos de Redux. O klo-gba.js apresenta um front simples de mais para precisar da complexidade que o Redux agrega.
Nós temos pouquíssimas operações IO-bound. Não temos nem mesmo um servidor para se comunicar. Além de que temos poucos componentes que mexem no estado global da aplicação.
Desse modo, podemos usar uma abordagem mais simples, já que o nosso problema é igualmente simples. Assim optamos em usar Provider Pattern. Com essa arquitetura nós definimos um contexto da qual os componentes utilizam para obter o estado global e, caso queiram modificar o estado global, solicitam que o provider faça a alteração.
Essa é uma boa forma de evitar o problema de explicitamente precisar descer diversas props em vários níveis na hierarquia de componentes, o chamado prop drilling.
Desde o começo da aplicação eu já sabia que o componente de selecionar a fase (SelectVision
) afetaria o estado global da aplicação, pois todos os demais componentes precisam se atualizar de acordo com a fase atualmente selecionada pelo usuário.
Aplicando o Provider Pattern, o SelectVision
precisa se comunicar com o provider responsável por armazenar as informações da fase selecionada (VisionProvider
). Toda a lógica de como atualizar as informações da fase fica no provider, não no componente!
Assim, quando o VisionProvider
é atualizado, todo os componentes que escutam esse provider também são re-renderizados. Isso é algo que deve-se tomar cuidado, pois se houver um má planejamento, re-renders indesejáveis podem acontecer!
Algo importante quando está desenvolvendo usando Provider Pattern é buscar quebrar os providers e isolar os ramos de componentes afetados por determinado provider, a fim de diminuir a quantidade de re-render desnecessários, tal como próprio Dan Abramov já explicou.
Assim o klo-gba.js tem atualmente cerca de 3 providers:
ROMProvider
, da qual armazena o buffer da ROM (uma ROM foi enviada? a função para atualizar a ROM…);VisionProvider
, da qual armazena as informações da fase atualmente selecionada (buffer do tilemap e objetos, as funções para atualizar a fase…);DisplayMapOptionsProvider
, da qual armazena as opções de exibição do mapa (exibir grid? ocultar os objetos?).
Um problema chato que tive é que, tanto a lib react-konva
como a @inlet/react-pixi
, não é possível passar contextos criados externamente. Essa comentário explica melhor esse bug.
A solução que fiz foi, no Tilemap
, simplesmente extrair todos os dados necessários nos contextos e passá-los como props para cada filho dele.
WebAssembly
Pois vamos encerrar esse capítulo com chave de ouro! Essa é a minha parte preferida e um dos maiores hypes no projeto.
Vocês se lembra que no quarto capítulo, Onde está o tilemap na ROM?, utilizamos um antigo projeto escrito em C para descomprimir o tilemap extraído na ROM? Então, isso funciona perfeitamente quando fazendo todo o processo de descompressão manualmente.. mas como podemos automatizar a descompressão e, além disso, rodá-la no browser? Afinal, uma página web não consegue rodar um código em C!
Reescrever todo aquele código em C para JS demandaria muito tempo, e eu já tinha confiança que aquele código em C funciona perfeitamente para os arquivos do tilemap do jogo.
Então que tal compilarmos o código em C para WebAssembly, e assim rodá-lo no browser?
Eu nunca havia usado WebAssembly antes, apenas conhecia por alto a teoria. Assim tive que aprender um pouco mais de como por a mão na massa. Nesses estudos, encontrei o popular projeto Emscripten. Ele já é bem maduro e antigo; existe desde a época que era popular compilar para asm.js, que é basicamente um subconjunto de JS, focando que seja bem mais otimizado para se processar.
Apesar de não ter sido o nosso objetivo, um outro ganho ao usar WebAssembly é uma melhor performance, pois o WebAssembly consegue performar muito bem em operações CPU-bound, da qual é exatamente o caso quando vamos descomprimir o tilemap.
Para compilar usando o Emscripten, basta executar emcc -s WASM=1 mySource.c
. Ao compilar, além de gerar o arquivo .wasm
, ele já gera um wrapper escrito em JS, o que facilita bastante a integração do WebAssembly com o resto da codebase.
O wrapper já carrega o .wasm
e expõe funções para usá-lo.
Outra maravilha do Emscripten é que ele consegue emular o sistema de arquivos. Pra que precisamos disso? Bem… o código original em C utiliza o sistema de arquivos para ler e escrever o buffer dos dados a ser comprimido e descomprimido. Para usá-lo nos devemos passar um filepath. Como eu realmente não queria mexer no código em C, pois isso seria bem suscetível a erros, busquei manter esse mesmo comportamento.
Estudando mais sobre o Emscripten, vi que é possível passar uma flag de compilação para ele emular o sistema de arquivos, e isso inclusive adiciona ao wrapper funções para manipular esse sistema de arquivos emulado! Com isso, foi possível preservar o máximo possível do código original.
A flag a ser usada é a -s EXTRA_EXPORTED_RUNTIME_METHODS=["FS"]
.
Porém, nem tudo são flores…
Eu conseguia compilar com sucesso o código em C da descompressão, e rodava sem problema no projeto de exemplo que o Emscripten gera, porém, ao tentar usá-lo dentro do klo-gba.js, não funcionava! Fiquei um tempo sem saber o que estava acontecendo, até que entendi que o problema é o Webpack. What!? O Webpack?? Sim. Ele mesmo.
Uma das configurações do Webpack é renomear os arquivos para evitar problema de cache no browser. Assim, o arquivo que antes se chamava lzss.wasm
passaria a se chamar algo como 0b549596b.wasm
. Ao renomear esses assets, o Webpack também atualiza os imports dentro do código, porém, isso não funcionava para o wrapper gerado pelo Emscripten, e assim ele não conseguia encontrar o .wasm
. Esse mesmo erro já havia afetado outras pessoas, e a solução que encontrava era basicamente usar o file-loader e realizar um patch na função locateFile
do wrapper. E assim temos:
const huffmanWasm = require('./wasm/huffman.wasm')
const huffmanModule = require('./wasm/huffman.js')({
locateFile (path) {
if (path.endsWith('.wasm')) {
return `node_modules/scissors/dist/${huffmanWasm}`
} return path
},
})...
A razão de acrescentar node_modules/scissors/dist/
antes do nome do arquivo é que, como o scissors é um projeto da qual é dependência do brush, é lá onde estarão os arquivos .wasm
do ponto de vista do brush.
O PR que adiciona essas features malucas com o WebAssembly é esse aqui.
A partir desse momento, me parecia que tudo funcionava perfeitamente, porém, percebi que tinha problema ao fazer deploy.
A razão é que, ao realizar o build de produção e fazer deploy, não existe um caminho chamado node_modules/scissors/dist/
. Assim, precisamos usar o loader copy-webpack-plugin para copiar os arquivos .wasm
de dentro do scissors e move para o brush. Esse commit é o que faz esse fix. (veja o PR desse commit para ter um melhor contexto)
Honestamente, eu não gostei lá tanto desse fix, pois cria um encapsulamento no build entre o brush e o scissors. Uma das ideias que me passaram para resolver melhor esse bug é desabilitar a renomeação aos arquivos .wasm
que o Webpack faz, mas não cheguei a testar essa abordagem (aceito colaborações no projeto!).
Após criar um pequeno módulo encapsulando a lógica de interação com o WebAssembly, a função para extrair uma fase é bem limpa:
const extractTilemap = (romBuffer, [addressStart, addressEnd]) =>
romBuffer.slice(addressStart, addressEnd)
|> huffmanDecode
|> lzssDecode
Repare que a função para extrair uma fase tem míseras três linhas! Ah, e note como é elegante o pipe-operator =^-^=
A partir desse momento, o principal já estava funcionando, afinal, já conseguíamos descomprimir no browser uma fase do jogo. Mas que tal melhorarmos um pouco mais?
O processo para compilar usando o Emscripten implica que a pessoa o tenha em seu computador e execute alguns comandos… então que tal automatizar isso com um script em shellscript e Docker?
Assim encontrei uma imagem do Emscripten feita pela comunidade (não existe uma oficial), e escrevi o seguinte shellscript:
function docker_run_emscripten {
local filename="$1" echo "Compiling $filename..." docker run \
--rm -it \
-v $(pwd)/scissors/src/wasm:/src \
trzeci/emscripten \
emcc -s WASM=1 -s ALLOW_MEMORY_GROWTH=1 -s MODULARIZE=1 -s EXTRA_EXPORTED_RUNTIME_METHODS=[\"FS\"] -s EXPORT_NAME=\"$filename\" -o ./$filename.js $filename.c
}docker_run_emscripten huffman
docker_run_emscripten lzss
Com isso, agora fica bem mais fácil a instação do ambiente para executar o klo-gba.js!
Finalizando…
Okay, vimos várias decisões realizadas durante o desenvolvimento do klo-gba.js, e a partir desse momento já temos o nosso web app para customizar as fases do jogo do Klonoa funcionando! Que bacana!
Falta mais alguma coisa? Bem… digamos que sim…
Nesse primeiro momento nós conseguíamos customizar uma fase e salvá-la na ROM, porém, com a seguinte limitação: a fase seguinte à customizada não funcionaria mais! Isto é, se modificarmos a fase N, poderíamos jogar a nossa fase customizada N, mas o jogo crasharia ao acessar a fase N+1.
Qual a razão desse bug e como podemos corrigi-lo? No próximo capítulo (o penúltimo!) falaremos isso em detalhes, e abordaremos tanto o front como também retornaremos a falar sobre engenharia reversa.