← voltar
technical

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

Campanha rodando: primeira fase do jogo

Na parte 1 eu expliquei a ideia geral: pegar o executável do Populous: The Beginning, traduzir todo o código dele para C de uma vez só (recompilação), e reescrever as peças do Windows que o jogo usa para funcionarem no Mac.

O menu apareceu. Mas entre aparecer o menu e dar pra jogar de verdade, teve uma sequência de problemas que acho a parte mais interessante da história. Cada um deles é um pedacinho de como o Windows (e jogo de 1998) funciona por baixo.

Problema 1: o jogo travava esperando ele mesmo

Logo no começo, o jogo simplesmente congelava. Nenhum erro, nada.

O que estava rolando: o Populous usa threads. Pensa em threads como várias pessoas trabalhando dentro do mesmo programa ao mesmo tempo. Uma cuida da tela, outra fica escutando o teclado, outra o mouse.

Pra simplificar, a gente tinha feito uma regra: só uma thread trabalha por vez, as outras esperam a vez delas. Só que a thread principal ficava num loop do tipo "enquanto o teclado não estiver pronto, continua esperando", sem nunca passar a vez. E a thread do teclado nunca ganhava a vez de ficar pronta. Clássico caso de duas pessoas segurando a porta uma pra outra.

A solução foi fazer o código traduzido "oferecer a vez" toda vez que passa por um loop. Mais pra frente isso voltou a dar problema na expansão: as threads passavam a vez tantas vezes que o jogo caiu para 3 quadros por segundo. Aí a regra virou "só passa a vez depois de ter trabalhado pelo menos 2 milissegundos". É o mesmo princípio que os sistemas operacionais usam pra dividir o processador entre programas.

Problema 2: o jogo esperava uma internet de 1998

O segundo crash foi curioso. O jogo, antes mesmo de mostrar o menu, perguntava para a biblioteca de rede quais "provedores de conexão" existiam no computador (internet, modem, rede local). No Windows de 1998 sempre existia pelo menos um. No nosso caso, zero. E o jogo nunca tinha sido testado num computador sem nenhum, então ele tentava ler o primeiro item de uma lista vazia e caía.

A correção foi a gente fingir que existem três provedores. Esse tipo de coisa aparece bastante em projetos assim: o jogo funciona no mundo real porque o mundo real sempre teve certas coisas, e você só descobre quais quando elas faltam.

Problema 3: o mouse clicava no lugar errado

O mouse do Populous não usa a posição da seta do sistema. Ele lê só o movimento: "o mouse andou 3 pra direita e 2 pra cima", e o próprio jogo desenha a setinha. Por isso a gente teve que capturar o mouse dentro da janela e mandar só os movimentos.

E tinha uma pegadinha a mais: o jogo processa o clique antes de processar o movimento que veio junto. Então nos testes automáticos, que moviam e clicavam ao mesmo tempo, o clique sempre caía no lugar antigo. Com um mouse de verdade isso não acontece, porque ninguém move e clica no mesmo milissegundo.

Problema 4: os vídeos saíam pretos (e depois quase certos)

O jogo abre com vídeos (o logo da Bullfrog, a introdução da xamã). Primeiro eles saíam pretos. Descobrimos que, para os vídeos, o jogo muda a tela para outro modo de cor, de 16 bits, que a gente ainda não sabia mostrar.

Corrigido isso, os vídeos apareceram, mas lentos e com pequenos quadradinhos errados na imagem. A lentidão era porque o player de vídeo usa o som como relógio: ele só avança o quadro quando o áudio avança. Sem som funcionando, cada quadro esperava quase meio segundo.

Os quadradinhos errados foram o bug mais difícil do projeto. E é aqui que entra a parte que eu acho mais legal de tudo.

Como achar um erro em 6,3 milhões de instruções

O decodificador de vídeo executa mais de 6 milhões de instruções para cada quadro. Em algum lugar ali, alguma tradução estava levemente errada. Ler o código não ia resolver.

A técnica que usamos se chama teste diferencial: rodar o mesmo trecho em dois lugares e comparar.

Como achar o erro: rodar o mesmo trecho em dois lugares e comparar
  1. O app tira uma "foto" de toda a memória do jogo logo antes de decodificar um quadro.
  2. Essa mesma foto é carregada no Unicorn, um emulador de processador Intel de código aberto, que serve de referência ("é assim que um PC de verdade faria").
  3. Os dois executam o quadro e vão anotando o estado depois de cada instrução.
  4. Uma ferramenta compara as duas listas e aponta a primeira instrução em que elas divergem.

Na primeira rodada, a diferença apareceu no passo 202.844: um "se for menor, pula" que pulava diferente. Era um bug no tradutor numa otimização de comparações. Corrigido, apareceu outra diferença no passo 5.126.457, dessa vez numa conta com números decimais. O processador Intel antigo tem um modo de precisão que o Windows configura de um jeito específico em todo programa, e a gente não estava reproduzindo isso.

Depois das duas correções: 6.338.491 instruções idênticas às da referência. E os vídeos ficaram iguais aos que o ffmpeg (uma ferramenta de vídeo padrão) gera a partir dos mesmos arquivos.

Vídeo de abertura rodando no app

A gente também fez uma versão menor dessa ideia para o tradutor inteiro: cada tipo diferente de instrução que o jogo usa roda milhares de vezes com valores aleatórios no Unicorn e no nosso código, e o resultado tem que bater. Isso dá uma segurança bem maior do que só "abri o jogo e parece ok".

Problema 5: o som

O jogo não toca som direto. Ele usa uma biblioteca de áudio 3D de terceiros, a QMixer, que vem como um arquivo separado. A primeira ideia seria reescrever a QMixer. Mas aí veio uma ideia melhor: se o recompilador traduz o jogo, ele também traduz a QMixer. Então a gente recompilou a biblioteca de som junto.

Só que ela se recusava a funcionar. Ela própria tem um modo de diagnóstico escondido que escreve um log, e ativando ele deu pra ver onde parava. Duas exigências: o computador precisava ter pelo menos um "dispositivo de áudio" do tipo antigo do Windows, e o sistema de som precisava suportar posicionamento 3D (o som vir da esquerda ou da direita dependendo de onde está na tela). Implementadas essas duas peças, o som e a música apareceram.

A expansão de brinde

Populous tem uma expansão, Undiscovered Worlds, que vem no mesmo pacote do GOG. Como o recompilador é genérico, foi só apontar para o executável dela. Funcionou praticamente de primeira, com as 12 fases novas.

Undiscovered Worlds rodando

E o multiplayer?

Chegou perto. O jogo já lista as conexões, cria uma partida e uma segunda cópia do jogo na mesma rede enxerga ela. Mas o "entrar na partida" ainda não fecha. O Mac e o Windows tratam conexões de rede de jeitos diferentes em vários detalhes pequenos, e cada um precisou de um ajuste. Decidi pausar e focar no single player. O que foi descoberto ficou documentado para quem quiser continuar.

Abrindo o projeto

No fim, publiquei tudo no GitHub, com licença MIT: OpenPopTB-MacOS. Documentação em inglês, explicando como usar, como contribuir e tudo que a gente descobriu sobre o jogo, com endereços e detalhes, para ninguém ter que redescobrir.

Uma decisão importante foi sobre o que não publicar. O app pronto contém o código do jogo traduzido, e esse código é da EA. Por isso o repositório só tem o nosso código (o tradutor e as peças de Windows) e nenhuma linha do jogo. Quem quiser jogar precisa ter o jogo comprado: aponta a pasta do jogo e o próprio Mac gera o app.

É diferente do OpenRCT2, que pode distribuir o app pronto porque reescreveu tudo e não tem código original nenhum dentro. O próximo passo do projeto é chegar perto disso na experiência: um app para baixar que, na primeira vez que abre, pega o instalador do GOG, extrai e recompila tudo sozinho, na máquina de quem comprou.

Gameplay

Sobre fazer isso com um agente de IA

Vale falar disso porque é a parte que mais me chamou atenção. Eu não escrevi o tradutor nem o código das peças do Windows. Quem escreveu foi o Claude Code. Mas também não foi um prompt e pronto.

O papel que fiz foi bem parecido com o de PM num time: definir o objetivo ("só para quando terminar o port"), decidir prioridades (pausar multiplayer, focar em single player), tomar as decisões que tinham trade-off (publicar o binário ou não, licença, como lidar com a questão legal) e cobrar evidência. Um exemplo: o agente fazia testes automáticos com screenshots e gravação de áudio, então quando dizia que algo funcionava, dava pra ver a prova.

O que me impressionou foi a forma de resolver problemas. Quando a abordagem de reescrever à mão se mostrou lenta demais, ele mudou de estratégia. Quando um bug não aparecia lendo código, ele construiu uma ferramenta para comparar com uma referência. Isso é muito mais próximo de como um bom engenheiro trabalha do que eu esperava.

Resumindo: o jogo original, inteiro, roda nativo no Mac, com som, vídeo e expansão. O caminho até lá passou por entender coisas que o Windows faz sem ninguém perceber, e por um jeito bem metódico de achar erros: comparar com uma referência confiável até achar a primeira diferença. Se você tem o jogo e um Mac com chip da Apple, o repositório está aberto. E se quiser ajudar com o multiplayer ou com gráficos melhores, melhor ainda.

Iago Macedo
// newsletter

receba os próximos posts por e-mail

um e-mail quando sair texto novo. sem spam, e dá pra sair quando quiser.