Fazendo um jogo de 1998 rodar nativo no Mac (parte 1)

Populous: The Beginning é um daqueles jogos que marcaram minha infância. Você é uma xamã, comanda uma tribo, lança feitiços que levantam montanhas, abrem vulcões e afundam ilhas inteiras. Saiu em 1998, para Windows, e até hoje dá pra comprar no GOG.
O problema: eu uso Mac 90% do tempo. E não existe versão de Mac.
Dá pra rodar com CrossOver ou Wine, que são programas que "fingem" ser Windows. Funciona mais ou menos. Mas fiquei com uma pergunta na cabeça: será que dá pra fazer o jogo virar um app de Mac de verdade? Um app nativo, que abre com um clique, sem nada no meio?
Spoiler: deu. E o mais curioso é como cheguei lá. Fiz o projeto inteiro com o Claude Code, já que não sei codar, e nessa pequena série vou tentar explicar os conceitos por trás e o que a gente fez. Sem entrar fundo demais, mas também sem fingir que foi um prompt e pronto.
Por que um jogo do Windows não roda no Mac?
Tem dois motivos, e vale separar.
O primeiro é o processador. Um programa, no fundo, é uma lista enorme de instruções bem simples: "soma esses dois números", "guarda isso nessa posição da memória", "se for maior, pula pra tal lugar". Cada família de processador fala uma "língua" diferente de instruções. O Populous foi escrito para processadores Intel de 1998 (a língua se chama x86, na versão de 32 bits). Os Macs atuais usam chips da própria Apple (M1, M2, M3...), que falam outra língua, chamada ARM.
A Apple tem um tradutor embutido, o Rosetta 2, que converte programas Intel para os chips novos. Só que ele não traduz programas de 32 bits. E o Populous é de 32 bits.
O segundo motivo é o sistema operacional. O jogo não conversa com o hardware diretamente. Ele pede coisas para o Windows: "abre uma janela", "toca esse som", "qual tecla o usuário apertou?". Essas peças do Windows (chamadas de APIs, e no caso de jogos daquela época, o famoso DirectX) simplesmente não existem no macOS.
Então pra rodar no Mac a gente precisa resolver as duas coisas: traduzir a língua do processador e substituir as peças do Windows.
Os três caminhos possíveis
Quando alguém quer trazer um jogo antigo pra uma plataforma nova, existem basicamente três jeitos.

- Reescrever: alguém estuda o jogo original e escreve tudo de novo, do zero. É o que o OpenRCT2 fez com o RollerCoaster Tycoon 2. O resultado é incrível, mas levou anos de trabalho de uma comunidade inteira.
- Emular: um programa finge ser o computador antigo e vai executando o jogo instrução por instrução, traduzindo enquanto roda. É o que o DOSBox faz, e mais ou menos o que o Wine/CrossOver faz com as peças do Windows. Funciona com quase tudo, mas é um intermediário sempre ligado.
- Recompilar: em vez de traduzir enquanto o jogo roda, traduz o código inteiro uma única vez, antes, e gera um programa novo, nativo da plataforma. Pensa como a diferença entre um intérprete que traduz uma conversa em tempo real e alguém que traduz um livro inteiro e manda imprimir a versão em português.
Esse último caminho ficou famoso nos últimos anos com projetos que trouxeram jogos de Nintendo 64 para PC com resultados impressionantes. E foi o caminho que a gente seguiu.
A primeira tentativa (que não foi por aí)
Antes da recompilação, a primeira abordagem foi a do OpenRCT2: entender o jogo e reescrever as partes à mão. Usamos o Ghidra, uma ferramenta gratuita da NSA (sim, aquela NSA) que pega um executável e tenta mostrar o código dele de forma legível.
Deu pra entender como o jogo guarda as fases (um mapa de alturas de 128 por 128 pontos, com o mundo dando a volta nas bordas, tipo um donut) e desenhar o terreno com as texturas originais.

Bonito, mas olhando pra isso dá pra perceber o tamanho do problema. É só o chão. Faltava tudo: as pessoas, os prédios, os feitiços, a inteligência artificial das tribos, o som, os menus, os vídeos. O jogo tem perto de 5 mil funções. Reescrever uma por uma seria aquele projeto de anos.
Foi aí que a gente mudou de estratégia.
Como a recompilação funciona
A ideia é: em vez de entender cada função, a gente traduz todas automaticamente. Separando em etapas, é mais ou menos assim:

- Descobrir onde está o código. Um executável mistura código com dados (textos, tabelas, números). O primeiro trabalho é mapear o que é instrução e o que não é. O recompilador começa pelo ponto de entrada do programa e vai seguindo cada caminho possível: toda vez que vê "pula para tal lugar" ou "chama tal função", vai lá e continua. No fim, achou 4.984 funções e cerca de 435 mil instruções.
- Traduzir cada instrução para C. C é uma linguagem de programação "neutra", que qualquer computador sabe compilar. Cada instrução Intel vira uma linha de C que faz exatamente a mesma coisa. Uma soma vira uma soma, um pulo vira um pulo.
- Compilar para o Mac. O compilador da Apple pega esse C e gera código nativo para os chips M. Aqui entra uma vantagem legal: o compilador moderno ainda otimiza o resultado.
Ninguém lê o código gerado. São 31 MB de C que parecem escritos por uma máquina, porque foram. O importante é que o comportamento é o mesmo do original.
E as peças do Windows?
Traduzir a língua do processador resolve metade. A outra metade são aquelas peças do Windows que o jogo pede o tempo todo.

O jogo traduzido continua achando que está num PC com Windows 98. Quando ele pede "abre uma janela", essa chamada cai num código nosso que abre uma janela de Mac. Quando ele pede "toca esse som", a gente toca no alto-falante do Mac. Foram reescritas algumas dezenas dessas peças: gerenciamento de arquivos, janela, imagem (DirectDraw), som (DirectSound), teclado e mouse (DirectInput).
Pra conversar com o Mac a gente usou a SDL3, uma biblioteca aberta muito usada em jogos que já sabe lidar com janela, áudio e controles em vários sistemas.
Um detalhe que acho elegante: nenhum dado do jogo vai dentro do app. Os gráficos, sons, fases e vídeos continuam vindo da pasta do jogo que você comprou. O app só sabe "falar" com eles.
O primeiro sinal de vida
A primeira vez que rodou, o jogo chegou bem longe na inicialização e parou pedindo uma peça que ainda não existia (o DirectDraw). Isso por si só já era uma ótima notícia: significava que milhares de instruções traduzidas tinham rodado certinho até ali.
Algumas peças depois, apareceu isso:

Pode parecer pouco, mas é o jogo original desenhando a própria tela, pixel por pixel, rodando nativo num Mac. Logo depois veio o menu principal. E aí a sensação foi de "ok, isso vai dar certo".

Mas "dar certo" ainda estava longe. O menu aparecia, mas o mouse não clicava onde devia, o vídeo de abertura saía preto, não tinha som nenhum e o modo multiplayer derrubava o jogo. Cada um desses problemas acabou ensinando alguma coisa sobre como o Windows funciona por baixo.
Na parte 2 eu conto como a gente caçou esses bugs (inclusive um que estava escondido no meio de 6,3 milhões de instruções), como colocamos o som e os vídeos pra funcionar, e como o projeto virou um repositório aberto no GitHub.
Resumindo essa primeira parte: rodar um jogo antigo num computador novo é um problema de duas camadas, a língua do processador e as peças do sistema operacional. A recompilação resolve a primeira traduzindo tudo de uma vez, e a segunda a gente resolve reescrevendo só as peças do Windows que o jogo usa. É bem menos trabalho do que reescrever o jogo inteiro, e o resultado é o jogo original, inteiro, rodando como app de Mac.
Links úteis
- Repositório do projeto: github.com/iagodemacedo/OpenPopTB-MacOS
- OpenRCT2, o exemplo clássico de reescrever um jogo
- Ghidra, a ferramenta de engenharia reversa
- SDL, a biblioteca usada para conversar com o Mac

