Sistema antigo, contrato novo.
Num sistema antigo, o que ele faz de verdade costuma morar só no código, ou só no executável. Nós lemos esse sistema, escrevemos o que ele promete num arquivo que uma máquina confere, geramos o sistema novo a partir desse arquivo e deixamos um avaliador que reprova quando o novo responde diferente.
Os três casos, do sistema antigo ao avaliador.
Quatro passos, e o último diz não.
Ler o que existe
O executável, quando é só o que sobrou, ou o código-fonte. Lido como dado: nada do sistema antigo é executado para isso.
Escrever o contrato
Cada tela, rota, botão, tabela e regra vira uma lista conferível. Mesma entrada, mesmo arquivo, byte a byte.
Gerar o sistema novo
Do contrato, e só dele, sai o sistema na tecnologia nova. O que não deu para traduzir fica marcado, não inventado.
Conferir e reprovar
Um avaliador compara o antigo com o novo e reprova a diferença. Sem ele, "ficou igual" é opinião.
O que já foi medido.
- Delphi → web34 de 34telas idênticas ao código-fonte, lidas só do executável do HeidiSQL 12.21.
- Laravel → FastAPI19 de 19endpoints respondem igual (81 de 81 passos), com controle negativo.
- O jogo5 de 12funções do Chocolate Doom reescritas iguais ao fonte só pelo executável; 6 parciais.
Todo número desta página sai de um arquivo de evidência publicado aqui mesmo, em /dados/indice.json. Agentes de IA: comecem por /llms.txt. Para rodar o leitor de Delphi aqui mesmo, a aba Experimente.
Do executável Delphi para a web.
Pegamos o HeidiSQL 12.21, um programa Delphi de código aberto, e lemos só o executável oficial, sem abrir o código-fonte. Em 5,3 segundos saiu tudo o que as telas prometem. Depois conferimos contra o código: 34 de 34 telas idênticas.
O caminho inteiro, do arquivo .exe à tela na web.
Um arquivo de 25 MB, lido como bytes, sem rodar o programa.
- 38telas
- 1.634componentes
- 1.264eventos de tela
- 199comandos SQL
- 48tabelas citadas
Cada número vem do contrato gerado do executável oficial (sha256 nos créditos) e é refeito igual a cada execução. Em JSON: caso.json.
Escolha uma tela e clique num botão: aparece o que ele faz no sistema original.
Onde existe código-fonte, o executável tem que dizer a mesma coisa. Disse.
| pelo executável | pelo código-fonte | |
|---|---|---|
| Telas do projeto | 34 | 34 |
| Telas idênticas, componente por componente | 34 de 34 | |
| Eventos nessas telas | 1.264, os mesmos dos dois lados | |
| Telas embutidas que nada abre | 4 | não aparece lendo o código |
Um achado que só o executável mostrou
A tela de sincronização de bancos (TfrmSyncDB) está dentro do executável, com botões e eventos, mas a classe dela não está: nada no programa abre essa tela, e o compilador descartou o código. Lendo só o fonte, ela parece viva. É o tipo de coisa que decide o escopo de uma migração: não se reescreve o que ninguém usa.
O executável não traz o código dos botões. A engenharia reversa traz.
Escolhemos 5 botões, cada um achado por um texto que só ele usa no código-fonte. A ferramenta de engenharia reversa achou o texto no binário, a função que o usa e escreveu essa função de novo, em pseudo-código. Depois comparamos com o fonte, fato por fato (botoes.json).
| Tela e botão | O que faz, lido do binário | Contra o fonte |
|---|---|---|
PreferênciasbtnRestoreDefaultsClick | Pergunta se pode apagar todas as preferências; se a resposta não for Cancelar, apaga cada valor salvo e recarrega a tela. | igual ao fonte |
SobrebtnDonatedOKClick | Grava o e-mail, consulta se é doador e mostra uma de três mensagens; esconde o botão de doar quando confirma. | igual ao fonte |
Ordenação de dadosbtnAddColClick | Cria um critério de ordenação com a primeira coluna ainda não usada, registra no log e redesenha os controles. | igual ao fonte |
Reformatar SQLbtnOkClick | Formata o SQL com um de três formatadores, mede o tempo e registra no log; se der erro, mostra a mensagem e não fecha a janela. | igual, menos o bloco de erro |
Editor de textomenuFormatCodeOnceClick | Formata o texto como JSON, SQL ou XML conforme o realce ativo, ou apita; se der erro, apita e registra no log. | igual, menos o bloco de erro |
- 3de 5 iguais ao fonte
- 18 de 18fatos do caminho normal
- 0 de 3fatos do tratamento de erro
- 18 minde análise, uma vez
- 0,1 spor pergunta depois
O que ainda escapa: o tratamento de erro
Nos 2 botões que têm bloco de erro, o caminho normal saiu inteiro e o bloco de erro não apareceu. Num deles, o texto escolhido só existe dentro do bloco de erro, e por isso a busca pelo texto não chegou à função: ela foi achada pela tabela que o Delphi usa para ligar cada evento ao seu código. Em 5 de 5 botões, a função apontada pela engenharia reversa é exatamente a que essa tabela indica. Numa migração, o tratamento de erro desses botões se confere à parte.
Quatro passos, cada um com a sua conferência.
Ler o executável
O Delphi guarda cada tela dentro do .exe. Lemos esses blocos como dados: telas, componentes, ligações e eventos. O programa nunca é executado.
Montar o contrato
Tudo vira uma lista do que o sistema promete: cada botão, o evento dele, o SQL que aparece. Mesmo arquivo, mesmo resultado, byte a byte.
Conferir com o fonte, quando existe
Lemos o código pelo arquivo de projeto e comparamos tela por tela. Aqui, 34 de 34 idênticas.
Subir para a web
As telas viram HTML, cada evento ligado à operação do contrato. Um conferidor reprova se faltar um componente ou um evento: faltaram 0.
O que o executável não entrega, e como fechamos.
- O código de todos os botões. A engenharia reversa leu 5 botões, não os 1.264 eventos. Cada um custa uma consulta rápida depois da análise inicial, mas o bloco de erro ainda precisa ser conferido à parte.
- SQL montado em tempo de execução. No código-fonte do HeidiSQL, 195 comandos são montados com variáveis; a tabela real só aparece rodando. Ficam marcados para conferir com quem conhece o sistema.
- O desenho. A versão web usa as posições originais da tela. É o ponto de partida para redesenhar, não o visual final.
Uma API em PHP reescrita em Python, conferida requisição por requisição.
O RealWorld ("Conduit") é uma especificação pública de API, com uma implementação em Laravel 9.17.0. Lemos o código PHP, escrevemos o contrato da API, geramos dele um app FastAPI e mandamos as mesmas requisições aos dois: 19 de 19 endpoints responderam igual.
Mesmo status, mesma resposta, mesmo banco, depois de cada passo.
- 19 de 19endpoints iguais
- 81 de 81passos iguais
- 0 de 19no controle negativo
- 0diferenças no esquema de 10 tabelas
- 1pendência no contrato
O controle negativo: o avaliador sabe dizer não
Para provar que o placar não é cego, alteramos um único artigo só no banco do app novo e rodamos tudo de novo: 0 de 81 passos iguais. O avaliador acusou a diferença em cada um, com o motivo, por exemplo: resposta $.articles[0].body: "…" != "alterado"
Em JSON: resultado.json (passo a passo), resultado-negativo.json, realworld.json (contrato e rotas) e cobertura-gerador.json.
Inclui erro de validação, sem login, sem permissão, não encontrado e acento no título.
| Endpoint | Passos iguais | Veredito |
|---|---|---|
DELETE /api/articles/{slug} | 4 de 4 | igual |
DELETE /api/articles/{slug}/comments/{id} | 3 de 3 | igual |
DELETE /api/articles/{slug}/favorite | 1 de 1 | igual |
DELETE /api/profiles/{username}/follow | 2 de 2 | igual |
GET /api/articles | 14 de 14 | igual |
GET /api/articles/feed | 3 de 3 | igual |
GET /api/articles/{slug} | 4 de 4 | igual |
GET /api/articles/{slug}/comments | 3 de 3 | igual |
GET /api/profiles/{username} | 4 de 4 | igual |
GET /api/tags | 3 de 3 | igual |
GET /api/user | 3 de 3 | igual |
POST /api/articles | 6 de 6 | igual |
POST /api/articles/{slug}/comments | 3 de 3 | igual |
POST /api/articles/{slug}/favorite | 2 de 2 | igual |
POST /api/profiles/{username}/follow | 3 de 3 | igual |
POST /api/users | 5 de 5 | igual |
POST /api/users/login | 5 de 5 | igual |
PUT /api/articles/{slug} | 6 de 6 | igual |
PUT /api/user | 5 de 5 | igual |
framework: método não permitido | 1 de 1 | igual |
framework: rota inexistente | 1 de 1 | igual |
Do PHP ao contrato, do contrato ao Python, e um avaliador no meio.
Contrato
O código PHP é lido pela árvore sintática, nunca executado: 19 rotas, 10 tabelas com 54 colunas, 4 models com 12 relações, 9 validações e 8 formatos de resposta.
Gerador
Do contrato sai um app FastAPI que roda sozinho: rotas, validações, tabelas e o código do app traduzido. Trecho sem tradução responde "não implementado" em vez de inventar.
Avaliador diferencial
Os dois apps sobem com cópias idênticas do mesmo banco. Cada passo vai aos dois; status, resposta e o banco inteiro são comparados. Diferença sai com o motivo.
Num app grande, o gerador ainda não cobre nenhuma rota da API.
O BookStack v26.09.1 tem 340 rotas (82 da API), 40 tabelas e 502 classes. O contrato sai inteiro, mas o gerador cobre 0 de 82 rotas da API: toda rota da API passa pela sessão do Laravel, que a tradução ainda não tem. O que mais trava, em número de rotas:
| O que falta traduzir | Rotas da API que dependem disso |
|---|---|
Illuminate\Session\Middleware\StartSession | 82 de 82 |
session | 82 de 82 |
url | 48 de 82 |
League\Flysystem\WhitespacePathNormalizer | 41 de 82 |
file_get_contents | 38 de 82 |
Em JSON: bookstack.json. O placar do RealWorld vale para o que o cenário exercita; o BookStack mostra o que falta para um app grande.
Um jogo em C, sem nomes, lido só pelo executável.
O Chocolate Doom 3.1.1 é um port fiel do Doom original. Pegamos o executável oficial, PE32+ x86-64, e pedimos a uma ferramenta de engenharia reversa que reescrevesse 12 funções importantes. O código-fonte da mesma versão foi o gabarito: 5 saíram equivalentes, 6 parciais e 1 não foi achada.
Textos quase todos; nomes, nenhum.
- 5 de 12funções equivalentes ao fonte
- 6parciais, com o defeito anotado
- 89,8%dos textos do fonte achados no binário
- 1.501funções próprias achadas (o fonte define até 2.101)
- 0nomes do jogo recuperados
O fonte é teto, não alvo: o compilador descarta e embute funções, então 1.501 de 2.101 não é taxa de acerto. Em JSON: metricas.json e caso.json.
Pseudo-código da engenharia reversa contra o código-fonte.
| Função | Veredito | Chamadas (fonte / binário) | O que se viu |
|---|---|---|---|
D_DoomLoop | equivalente | 13 / 15 | as 13 chamadas aparecem em ordem; EnableLoadingDisk foi embutida (+M_CheckParm "-cdrom", DEH_String STCDROM/STDISK); printf virou puts. Nenhum nome recuperado. |
G_DoSaveGame | equivalente | 18 / 18 | o compilador duplicou o bloco de gravação nos dois caminhos (arquivo normal x recuperação); o pseudo-código mostra os dois, equivalentes ao fonte. |
P_WriteSaveGameHeader | equivalente | 4 / 3 | memset(name,0,16) virou atribuição de 16 bytes zerados; daí 3 chamadas em vez de 4. |
P_ReadSaveGameHeader | equivalente | 5 / 4 | return 0 na versão errada e return 1 no fim, como o fonte; memset embutido. |
G_VanillaVersionCode | equivalente | 0 / 0 | o switch do fonte virou aritmética; o pseudo-código é equivalente, mas não parece com o fonte. |
W_AddFile | parcial | 15 / 15 | W_Read é chamada por ponteiro (file_class->Read) e o Ghidra perdeu os argumentos no pseudo-código: o offset do diretório só aparece no assembly (0x140023260, MOV EDX,[RSP+0x2c]), também devolvido pelo REA. |
W_ReadLump | parcial | 3 / 3 | `if (size <= lidos) { }` vazio seguido de I_Error: lido ao pé da letra, a função sempre aborta. Os argumentos de W_Read também sumiram. |
W_GetNumForName | parcial | 2 / 2 | mesmo defeito: `if (-1 < i) { }` vazio e o retorno some. A mensagem aparece em 3 procedimentos porque o -O2 embutiu a função em W_CacheLumpName e W_ReleaseLumpName. |
G_DoLoadGame | parcial | 12 / 12 | as 12 chamadas batem, mas o `return` depois de fclose (cabeçalho inválido) sumiu, e o fim (`if (setsizeneeded) R_ExecuteSetViewSize(); R_FillBackScreen();`) saiu com a condição invertida e uma chamada a mais. |
TryRunTics | parcial | 12 / 5 | GetLowTic, PlayersInGame, SinglePlayerClear e TicdupSquash foram embutidas: 233 linhas de código corrido e 5 chamadas, contra 12 no fonte. Dá para seguir, não dá para comparar linha a linha. |
M_LoadDefaults | parcial | 5 / 6 | o Ghidra não tratou _assert como sem-retorno e grudou 3 funções vizinhas (M_Bind*Variable) no fim do procedimento. |
G_Ticker | não achada | – | o texto é referenciado em 0x14002ead6, mas nenhum procedimento contém esse endereço (not_in_procedure): o Ghidra não criou a função. O procedimento vizinho (0x14002e570) é G_WriteDemoTiccmd. |
Em JSON, com o motivo de cada veredito: funcoes.json.
Onde o pseudo-código engana, e em quais funções.
- O retorno antecipado some. Sai um
ifvazio seguido do erro: lido ao pé da letra, a função sempre aborta. Quem portar o código a partir do pseudo-código tem de conferir o assembly nesses pontos. Em:W_ReadLump,W_GetNumForName,G_DoLoadGame. - A chamada por ponteiro perde os argumentos. A leitura do arquivo é chamada por ponteiro, e o pseudo-código fica sem os argumentos; o deslocamento certo só aparece no assembly, que a ferramenta também devolve. Em:
W_AddFile,W_ReadLump. - A fronteira da função sai errada. Uma função ganhou três vizinhas coladas no fim; outra nem virou função, porque o endereço que usa o texto ficou fora de qualquer procedimento. Em:
M_LoadDefaults,G_Ticker. - Função embutida apaga a forma. O compilador embutiu funções pequenas: sobra um bloco corrido com bem menos chamadas que o fonte. Em:
TryRunTics. - Nenhum nome do jogo volta. O executável não tem tabela de símbolos: 3 procedimentos ganharam nome, e 0 deles é nome do jogo.
O formato do arquivo de dados do jogo, escrito só a partir do binário.
O WAD guarda mapas, sons e texturas; documentá-lo é o que permite editor de mapa, mod e preservação. O formato foi escrito a partir do pseudo-código e do assembly da função que abre o arquivo, e só depois conferido contra o fonte. Rodado no freedoom1.wad do Freedoom:
- IWADtipo do arquivo, lido do cabeçalho
- 3.163blocos de dados no diretório
- 36mapas, de E1M1 a E4M9
- 0blocos fora do arquivo
O diretório começa no byte 28.744.468 e termina exatamente no fim do arquivo. Em JSON: freedoom1.wad.json.
O leitor de executáveis Delphi, no seu navegador.
A mesma leitura do caso Delphi, rodando nesta página: escolha um .exe, .dll ou .bpl Delphi, o .zip do projeto ou um .dfm, e saem as telas, os eventos e o contrato em JSON. O arquivo não sai do seu computador: o Python roda dentro da aba, em WebAssembly.
O mesmo programa da linha de comando, sem servidor.
- Na primeira vez o navegador baixa o Python 3.14.2 em WebAssembly (Pyodide 314.0.7, 13,5 MB sem compressão); depois fica em cache.
- Nenhum envio: a página só busca arquivos deste site. O arquivo escolhido é lido na própria aba.
- Mesmo resultado da linha de comando: o pacote que roda aqui é o mesmo do leitor de Delphi, copiado sem mudança. Há dois exemplos prontos para quem não tem um executável à mão.
- Licença do Pyodide: MPL-2.0, sem alteração nos arquivos.
A página do leitor e os dados de cada caso, sem raspar HTML.
- /experimente/: o campo
#arquivorecebe o arquivo;body[data-estado]diz em que ponto está; o resultado sai em#resultado-jsone o contrato inteiro em#contrato-json;window.delphiContratofaz o mesmo por código; comdocument.modelContext, a página registra as ferramentas WebMCPextrair_contratoecomparar. - /llms.txt: o que é este site e onde está cada dado, em texto simples.
- /dados/indice.json: os três casos, os números do placar e o endereço de cada evidência.
- Delphi: caso.json, telas.json (o contrato das telas), botoes.json.
- Laravel → FastAPI: realworld.json (contrato), resultado.json (placar), resultado-negativo.json, bookstack.json.
- O jogo: caso.json, metricas.json, funcoes.json, freedoom1.wad.json.
Programas de terceiros, código aberto, usados como caso de estudo.
A Genesis não tem relação com os projetos abaixo. Nada foi alterado em nenhum deles.
- HeidiSQL 12.21: criado por Ansgar Becker e colaboradores, licença GPL-2.0. Executável: release oficial 12.21, sha256 do
heidisql.exed4bc6b81906832aeed79e054ed094f5e15449c8fa91285538173d4a7f90db113. Código-fonte: repositório oficial, commiteffb854(Bump version for v12.21 release). - RealWorld em Laravel (
laravel-realworld-example-app): licença MIT. Repositório, commitc14fb83. - BookStack v26.09.1: licença MIT. Repositório, commit
31d94dd. - Chocolate Doom 3.1.1: licença GPL-2.0-or-later. Projeto; executável do pacote oficial, sha256 do
chocolate-doom.exe15e81c194df270fbd71450222e71846247926fabfb889e72e7d855903454d752. - Freedoom 0.13.0 (BSD-3-Clause): o arquivo de dados livre usado no lugar do Doom original. Pacote oficial.
- Engenharia reversa:
rea-agents@6.7.0(MIT) com o Ghidra 12.1.4 (Apache-2.0), sempre num contêiner sem rede, com o alvo só para leitura.
Tem um sistema parado no tempo?
Delphi, PHP, um executável sem código: manda o que existe. Devolvemos o contrato do sistema, o que ele faz tela por tela ou rota por rota, e o caminho para a tecnologia nova.