1 pontos por GN⁺ 2024-07-15 | 1 comentários | Compartilhar no WhatsApp
  • O RTS de 2001 Emperor: Battle for Dune tinha instalação, execução e jogo online instáveis no Windows moderno, e o EmperorLauncher é um patch que o devolve a um estado jogável
  • As melhorias principais se concentram em suporte a alta resolução, limite de 60 FPS, multiplayer online por IP direto, modo campanha cooperativa e contorno do processo de instalação quebrado
  • A implementação é composta por um launcher substituto para Emperor.exe, injeção de DLL direcionada ao Game.exe, patch de funções com Microsoft Detours, hook de renderização Direct3D 7, interceptação de winsock e um servidor WOL simplificado
  • O jogo online foi alterado para tunelar a estrutura antiga de P2P, portas aleatórias e NAT punching em uma única conexão cliente→servidor, de modo que apenas o host do servidor precise se preocupar com configuração de rede
  • Ele também cuida de tudo da instalação à execução, incluindo cópia dos arquivos do CD original, extração de .cab, aplicação do patch oficial v1.09, bypass do registro COM de WOLAPI.DLL e uma UI launcher em Win32

Onde Emperor: Battle for Dune emperrava

  • Emperor: Battle for Dune é um jogo de estratégia em tempo real criado em 2001 pela Westwood Studios e sucessor de Dune 2000
  • Em sistemas modernos, vários problemas ainda atrapalham a jogabilidade
    • Não roda em alta resolução adequada para telas atuais
    • No multiplayer, a velocidade da simulação do jogo não é limitada e fica rápida demais
    • O Westwood Online (WOL) não funciona mais, tornando difícil jogar multiplayer fora da LAN
    • A campanha cooperativa é um recurso exclusivo do online, então não pode ser usada em LAN
    • O instalador incluído no disco está quebrado
    • Vários efeitos visuais quebram nas altas taxas de quadros dos PCs modernos
  • O EmperorLauncher é um patch para resolver esses problemas, com download e código-fonte disponíveis

Substituição do Emperor.exe e inicialização do Game.exe

  • O Emperor.exe do jogo não é o executável real do jogo, mas um wrapper fino que inicia o Game.exe
  • Executar Game.exe diretamente não faz nada, então foi necessário analisar o processo de inicialização feito por Emperor.exe e recriá-lo em um launcher substituto
  • A análise foi feita com IDA
    • O IDA consegue desmontar executáveis e decompilar parte do código para algo parecido com C
    • Em binários sem informações de tipos e structs, foi preciso rastrear chamadas de função e uso da API do Windows
  • Antes de iniciar Game.exe, Emperor.exe cria um mutex e um handle de mapeamento de arquivo anônimo, processa dados lidos de Emperor.dat e os copia para o mapeamento
  • O processo pai envia mensagens do Windows para o ID da thread principal do processo filho obtido via CreateProcessA, repassando o valor do handle do mapeamento de arquivo
    • O ID de mensagem customizado usado era 0xBEEF
    • Os dados do mapeamento eram as três strings "UIDATA,3DDATA,MAPS", repassadas ao código de carregamento de assets do jogo
  • Em vez de reimplementar o código de descriptografia, foi colocado um utilitário de dump no lugar de Game.exe para gravar os dados recebidos em disco, e depois o launcher repetiu a mesma sequência

Injeção de DLL e patch de funções

  • Para aplicar os patches, era preciso executar código do usuário dentro do processo Game.exe, e para isso foi usado o método CreateRemoteThread + LoadLibrary
  • O processo de injeção segue esta ordem
    • Alocar um buffer na memória do processo de destino com VirtualAllocEx
    • Copiar a string com o caminho da DLL usando WriteProcessMemory
    • Passar o endereço de LoadLibrary e o buffer do caminho da DLL para CreateRemoteThread, carregando a DLL dentro do processo alvo
    • Quando o DllMain da DLL roda, o código de patch entra em ação
  • O processo foi iniciado em estado suspended e a DLL foi injetada antes da execução de main, garantindo execução de código antecipada
  • Para modificar funções existentes, foi usado o Microsoft Detours
    • O Detours substitui as instruções iniciais da função original por um salto que redireciona a chamada para a função substituta
    • As instruções originais sobrescritas são copiadas para uma área separada de memória, e é criado um wrapper que depois salta para o restante da função original, permitindo chamá-la também
  • Como as páginas de código das funções não são graváveis por segurança, foi necessário trocar as permissões com VirtualProtect e chamar FlushInstructionCache após a modificação

Restauração dos logs de debug

  • Havia chamadas no binário que pareciam logs de debug, mas a função de destino real era vazia e continha apenas ret
  • Parece que, no build de release, várias funções vazias foram fundidas no mesmo código, e uma delas era o logger de debug
  • No início, foi usada uma heurística que interpretava o primeiro argumento como ponteiro para string e verificava se apontava para caracteres ASCII imprimíveis
    • Acessos incorretos a ponteiros eram capturados e ignorados por meio de exceções SEH do Windows
    • Isso funcionava até certo ponto, mas ainda gerava falsos positivos e falsos negativos
  • Depois, o recurso de patch do IDA e scripts Python foram usados para mover os pontos de chamada de log para uma função vazia separada
    • Parte foi encontrada por heurística, e parte por padrões de push de constantes string seguidos de chamada
    • As centenas de pontos restantes foram anotadas manualmente
  • Os logs restaurados ajudaram a depurar o multiplayer WOL
    • Ao ver o log de assert "MyId == INVALID_ID" durante o processamento de SC_MESSAGE_YOUR_DETAILS, foi possível confirmar em um dump do Wireshark que o comando GAMEOPT estava sendo enviado incorretamente para todos os jogadores

Patch gráfico de Direct3D 7

  • Emperor é um jogo baseado em Direct3D 7, e o suporte moderno do Windows ao Direct3D 7 não é completo
  • O problema de alta resolução estava relacionado ao limite máximo de textura de 2048 na camada wrapper do Direct3D 7, e foi resolvido aproveitando o código de LegacyD3DResolutionHack de UCyborg
  • O jogo não lida corretamente com telas fora da proporção 4:3
    • A renderização em si funciona, mas a UI quebra como se tivesse sido ampliada demais
    • O offset do cursor renderizado no jogo também sai do lugar dependendo da distância até o centro da tela
  • A solução foi letterboxing em 4:3
    • Rodando o jogo em modo janela, é possível usar resoluções arbitrárias
    • O estilo da borda da janela é removido, e a janela do jogo é reparentada sobre uma janela preta em tela cheia
    • Também foi adicionada captura do mouse para evitar que o scroll nas bordas quebre em multi-monitor ou modo janela
  • O limite de framerate foi implementado com patch em IDirect3DDevice7::EndScene, ajustando para 60 FPS
    • EndScene é chamado uma vez no fim de cada quadro, então é um bom ponto para calcular atraso e fazer a thread dormir
    • Como o ponteiro de EndScene não é exportado diretamente, foi necessário hookar em sequência DirectDrawCreateEx e IDirect3D7::CreateDevice para obter o ponteiro da função pela vtable

Multiplayer online e substituição do WOL

  • O objetivo era ter multiplayer por IP direto sem infraestrutura de lobby ou hospedagem, usando apenas port forwarding e entrada manual de IP
  • O modo LAN funciona, mas como encontra servidores via broadcast UDP, não serve bem para jogar pela internet
    • O menu de LAN não tem opção de digitar o IP manualmente
    • No começo, tentou-se adaptar o chat da LAN para especificar IP, mas isso foi abandonado ao se confirmar que a campanha cooperativa era exclusiva do WOL
  • Para ressuscitar o WOL, eram necessárias duas coisas
    • Um servidor mestre WOL falso para o jogo saber aonde se conectar e qual partida iniciar
    • Um proxy para fazer os pacotes do jogo funcionarem sobre conexões por IP direto
  • A arquitetura WOL original tinha, além do master server, um servidor “mangler”, que aparentemente coordenava NAT punching
    • O servidor mangler original desapareceu, e o jogo travava esperando a resposta dele
    • No patch, as chamadas ao mangler foram removidas
  • Emperor usa um modelo de rede P2P e abre conexões bidirecionais entre cada par de jogadores, escolhendo portas aleatórias
    • Como todos os clientes precisariam receber conexões em portas abertas, isso não combina com a internet moderna
  • A solução foi interceptar funções winsock e tunelar todas as conexões em uma única conexão cliente→servidor
    • As mensagens que o cliente tentaria enviar ao servidor ou a outros clientes são interceptadas, encapsuladas com um cabeçalho e enviadas pela conexão única
    • Uma thread no lado do servidor recebe as mensagens e as redistribui ao destino correto
    • O jogo continua acreditando que opera em P2P, mas na prática só o host do servidor precisa cuidar da configuração de rede
  • Com essa configuração, foi possível iniciar e entrar em partidas de campanha cooperativa

Implementação de um servidor WOL simplificado

  • O servidor mestre WOL tinha uma estrutura mais próxima de um servidor IRC
  • Em xwis.net existia um servidor aparentemente operado por fãs e que, na época do texto, parecia ainda ter acesso à entrada DNS original do jogo, servserv.westwood.com
    • Emperor não funcionava perfeitamente com o xwis, mas ele serviu como referência para criação e entrada em lobbies
  • A implementação pública handle_wol.cpp do pvpgn-server também foi usada como referência
  • O motivo para criar um servidor próprio foi não depender da continuidade de servidores externos
    • O objetivo não era operar uma comunidade competitiva, mas fornecer o mínimo necessário para fazer partidas multiplayer funcionarem
  • O WOL mistura IRC padrão com comportamentos customizados
    • O lobby do jogo é um canal especial
    • As informações do lobby usam o topic do IRC
    • O chat do lobby usa PAGE em vez de PRIVMSG
    • A sincronização das configurações da partida usa mensagens GAMEINFO com conteúdo não ASCII
  • A implementação básica do servidor WOL foi concluída por tentativa e erro e, embora não seja robusta fora do fluxo normal, funciona

Instalador e aplicação do patch v1.09

  • O instalador original está quebrado, então era necessário um contorno em que o usuário copia o conteúdo do CD para o disco rígido e sobrescreve o setup por um alternativo
  • O EmperorLauncher inclui uma função de instalação que copia os arquivos do CD original e extrai arquivos .cab
    • .cab é um formato de arquivo compactado semelhante a zip, e o Windows já fornece uma interface para extração
  • Aplicar o último patch oficial, o v1.09, foi mais complicado
    • Não funcionou simplesmente extrair EM109EN.EXE com 7zip para obter os binários atualizados
    • Foi encontrado, dentro dos recursos do Windows, um recurso grande no qual era possível ver o cabeçalho de um executável
    • Os primeiros 4 bytes desse recurso eram o tamanho do arquivo, e o restante era o arquivo real
  • EM109EN.EXE extrai uma DLL embutida para um arquivo temporário, carrega essa DLL e depois executa a função RTPatch32@12 dentro dela
    • RTPatch era uma ferramenta de patch binário por diff
    • A ferramenta myRTP foi usada como referência para carregar e executar a DLL embutida diretamente
  • A DLL lia o caminho da instalação do registry, em vez de usar o caminho recebido por argumento, então foi necessário criar a chave de registry esperada para aplicar o patch

Tratamento dos Westwood Online Shared Internet Components

  • A instalação original inclui, além do Emperor em si, os Westwood Online Shared Internet Components
  • Sem esse componente, o WOL não funciona, e o arquivo principal é WOLAPI.DLL
  • WOLAPI.DLL é uma biblioteca de classes COM, e o Emperor cria os objetos COM dela com CoCreateClass
  • O registro COM tradicional registra CLSIDs e o caminho da DLL para todo o sistema em HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID
    • Esse método exige privilégios de administrador
    • E afeta o sistema inteiro além do processo do jogo
  • No patch, isso foi resolvido com redirecionamento de registry e OaEnablePerUserTLibRegistration, fazendo um registro por usuário
    • Não foi encontrada uma forma de registrar a class library apenas no escopo de um único processo
    • Tentar usar DllGetClassObject diretamente não funcionou

UI do launcher e resultado final

  • A etapa final foi uma UI simples de launcher para inserir IP e alterar configurações básicas
  • A UI foi escrita com controles Win32 puros
    • Para uma interface estática e simples, isso foi suficiente, embora a experiência de montar UI diretamente em Win32 tenha sido áspera
  • No fim, o EmperorLauncher se tornou uma ferramenta que reúne execução em sistemas modernos, alta resolução, limite de 60 FPS, multiplayer por IP direto, campanha cooperativa, instalação e aplicação de patch

1 comentários

 
GN⁺ 2024-07-15
Comentários do Hacker News
  • Este jogo tem um peso considerável para todo o gênero de estratégia em tempo real. Normalmente, quando se fala em RTS, pensa-se naquela estrutura em que camponeses coletam recursos e você os protege, e o RTS de Dune estava bem próximo desse modelo original
    Mas essa estrutura também acabou sendo assim por causa do romance original. Se não fosse isso, o gênero talvez tivesse seguido um caminho totalmente diferente. Por exemplo, a base em si poderia extrair recursos, enquanto o oponente faria pressão assediando construções, e a recompensa por controlar o mapa poderia vir em outra forma além do acesso a recursos

    • Para ser exato, este texto não é sobre Dune 2, e sim sobre Emperor: Battle for Dune
    • Como outra pessoa apontou, isto provavelmente é uma continuação de Dune 2 que você está lembrando
      Dune 1 também ajudou a preparar o terreno. No começo ele é mais próximo de uma aventura point-and-click, mas mais para o fim vira um jogo com gerenciamento de recursos e mineração, produção de tropas, combate e terraformação
      A parte final era completamente confusa para mim. O objetivo parecia ser tornar o planeta verde de novo, mas quando ele ficava verde não saía spice. Só que o imperador continuava exigindo mais entregas de spice, e se você não atingisse a cota era game over
      Também é bem possível que eu simplesmente não tenha entendido direito porque joguei isso quando era criança. Eu precisaria revisitar, mas pelos padrões de hoje talvez não tenha envelhecido tão bem, ou talvez leve bastante tempo para terminar
      Edit: eu não sabia que Dune 1 e 2 saíram no mesmo ano. Se for esse o caso, então o desenvolvimento de Dune 2 já estava em andamento, ou eles fizeram a engine e o jogo em menos de um ano. Mesmo hoje, com indies e ferramentas mais rápidas, é difícil imaginar
    • Eu sempre pensei em The Settlers(https://en.wikipedia.org/wiki/The_Settlers_(1993_video_game)) como o primeiro jogo de estratégia em tempo real
      Foi lançado em junho de 1993, então alguns meses depois de Dune, mas se os dois estavam sendo desenvolvidos ao mesmo tempo, talvez tenha sido um caso de vários “inventores” chegando à mesma ideia. Dizem que The Settlers foi influenciado por jogos de “deus” como Populous(https://en.wikipedia.org/wiki/Populous_(video_game)). Nesse modelo, o jogador tem poderes divinos, como alterar o terreno, mas não controla diretamente as unidades
    • Dawn of War é um bom exemplo de RTS que foge do paradigma clássico de mineração de minério
    • Talvez no fim das contas o gênero fosse acabar indo nessa direção mesmo. A história se apoia bastante no arquétipo “camponeses cuidam da base de recursos, governantes são estrategistas”
      Não é algo exclusivo de Dune; eu diria que a própria história em geral funciona um pouco assim
  • Ótimo texto e excelente trabalho. Eu fiz algo parecido uns 10 anos atrás, mas com Tiberian Sun, e o trabalho era corrigir o código de rede
    Entrar assim no código de outra pessoa dá uma sensação de conexão compartilhada. Descobri, para meu horror, que havia uma pilha totalmente separada para jogo por modem. Não era simplesmente TCP/IP passando por cima do modem
    Alguém deve ter passado meses escrevendo código sob medida para enquadramento, sincronização, tratamento de erros e o que fazer ao rediscar quando a conexão caísse. E, no entanto, quando o jogo foi lançado, esse código já estava praticamente obsoleto

    • Acho que isso era por outro motivo. O objetivo provavelmente não era conectar à internet discada, e sim ligar diretamente para outro modem
      Nunca usei isso pessoalmente, mas muitos jogos antigos tinham essa opção
  • Muito bom. Esta parte do texto me chamou a atenção:
    “Westwood Online (WOL) não funciona mais, então não dá para jogar multiplayer fora de LAN”
    Eu gostava de Command & Conquer quando era criança e conheço um pouco do lado cliente do Westwood Online
    Se bem me lembro, depois que o WOL saiu do ar, o XWIS.net deu bastante suporte. Talvez valha a pena o autor entrar em contato com a pequena comunidade de desenvolvedores de lá. Embora, a esta altura, também possa ser algo realmente em desaparecimento
    O trabalho feito pelo pessoal do XWIS chegou a ser reconhecido pela EA e, pelo que me lembro, ajudou bastante a manter o suporte ao WOL em C&C Renegade
    Também existe o projeto FreeRA, que é uma espécie de ancestral direto dos relançamentos recentes de C&C na Steam e em outras plataformas. Talvez eles também possam ajudar a reviver o WOL
    Como o WOL era encaixado por meio de uma biblioteca própria, é bem possível que substituir a biblioteca seja muito mais fácil do que fazer engenharia reversa de toda a pilha do WOL
    Edit: continuei lendo e vi que os componentes do WOL também foram corrigidos. Melhor ainda

  • Excelente texto. O autor parece uma pessoa tão divertida e inteligente que dá vontade de sair para tomar uma com ele à noite
    Eu gostei muito das explicações expansíveis, que são fofas e úteis ao mesmo tempo. Ler o texto parecia quase jogar um RPG de aventura com escolhas, o que foi uma experiência bem diferente
    E, sobre a observação “CS:GO só foi aposentado em 2023”, eu achei que CS:GO tivesse sido apenas rebatizado como CS2; estou enganado?

    • CS2 é visto por alguns como um downgrade em relação a CS:GO. Eu também acho isso
      Ouvi relatos de que até em PCs de torneio o CS2 não conseguia manter uma taxa de quadros decente, apesar de o mesmo hardware rodar CS:GO muito bem. Também há muitos relatos de usuários compartilhando resultados parecidos até em PCs de alto desempenho
      A Valve queria que o CS2 parecesse uma continuação de CS:GO, mas em vez de criar um jogo melhor e deixá-lo substituí-lo naturalmente, forçou a mudança sobre a base de jogadores. Como CS:GO era um jogo excelente, eu e outras pessoas ainda vamos ficar ressentidos por um bom tempo
    • O CS2 foi publicado com o mesmo ID de aplicativo do CS:GO, mas é um jogo totalmente refeito em uma engine nova. Não é apenas um rebranding
    • O CS2 usa a engine Source 2 com renderizador DX11 ou Vulkan, ao contrário do antigo CS:GO baseado em Source 1
  • É realmente divertido ver jogos antigos clássicos superando esses jogos modernos cheios de publicidade, monetização agressiva e feitos para pagar para vencer
    Basta a ajuda de um único hacker para o público abandonar esses jogos lixo. Em uma mídia duradoura, parece que as boas coisas do passado acabam vencendo as coisas medíocres do presente

  • Excelente texto e excelente esforço. Talvez dê para integrar isso de alguma forma com o nosso trabalho no CnCNet. Seria legal aparecer por lá para conversar

  • “vem com um modem de 28.8 BPS”
    Matriz ativa, claro. Um milhão de cores psicodélicas

    • A arquitetura RISC vai mudar tudo
  • Texto muito interessante e profundo. A quantidade de detalhes e conhecimento compartilhado sobre como fazer engenharia reversa e aplicar patches em jogos abandonados foi realmente ótima
    Eu vi esse jogo numa loja de usados do bairro, mas como só tinha jogado Dune II RTS, deixei passar. Agora pretendo pegar com certeza

    • Fico curioso se roda no Wine
  • Relacionado a isso, há um jogo moderno de estratégia em tempo real de Dune na Steam
    https://store.steampowered.com/app/1605220/Dune_Spice_Wars/

    • Chamar isso de estratégia em tempo real é um pouco forçado. Está mais para um jogo 4X, o ritmo é mais lento e o combate nem é tão importante
    • Independentemente de outras pessoas chamarem Spice Wars de 4X, o que mais chegou perto de um RTS moderno de Dune na prática foi Homeworld: Deserts of Kharak
  • “Design de UI é a minha paixão”
    Muito bom mesmo. Sinto falta desse tipo de escrita. Em vários aspectos, isso me lembrou posts de blog do Steve Yegge