Hackeie o Age of Empires III para alterar as configurações de qualidade do shader

No início de maio de 2020 - se você é como eu, a quarentena o fez refazer jogos que não são lançados há muitos anos.

E se você é ainda mais parecido comigo, em algum lugar você pode ter um disco do Age of Empires 3. Talvez esteja jogando em um Mac, talvez ainda não tenha atualizado para a Catalina e gostaria de comandar Morgan Black.

Então, você inicia o jogo, entra no menu principal e imediatamente percebe que algo está errado ... O menu parece nojento .


Se você está se perguntando o que exatamente é "nojento", preste atenção à água. Todo o resto também é terrível, mas é menos óbvio.


Então, você entra nas opções, aumenta todos os parâmetros ao máximo ... Mas o jogo ainda é feio.

Você percebe que as opções " Qualidade do sombreador " estão suspeitamente bloqueadas em " Baixa ".

Tentativa nº 1 - parâmetros de invasão


Nesse ponto, você começa a procurar a pasta do jogo, que supostamente foi criada em algum lugar da pasta Documents, porque o jogo foi em 2005 e todos fizeram isso.

O que você está procurando? Obviamente, o arquivo no qual os parâmetros estão armazenados. A interface do menu não nos permite alterar as configurações, mas somos complicados, certo?

Encontramos o XML necessário, porque o jogo é 2005 e, é claro, XML, onde encontramos a opção " optiongrfxshaderquality ", que é definida como 0. Parece que estávamos procurando, então aumentamos o valor para 100, porque não há muita qualidade. Aqui eu concordo com você.


Você também pode perceber que esse é um uso terrível do XML. Felizmente, ele não tem bons usos.

Satisfeitos conosco, lançamos o jogo. Infelizmente, nada mudou. Olhando novamente para o arquivo XML, vemos que o parâmetro é novamente definido como 0. O Ensemble Studios (que descanse em paz) parece com desprezo toda a sua engenhosidade.

Depois disso, poderíamos desistir . Este é um Mac, portanto, é improvável que os patches escritos pelos usuários sejam encontrados (na verdade, verifiquei - existem todos os tipos de soluções duvidosas que podem funcionar). É tudo sobre os gráficos. Parece corrigir o problema será difícil.

Mas não vamos desistir. Porque lembramos como deve ser a idade 3. Lembramos como admiramos essa bela água ondulada. Esses efeitos de partículas. Esses navios enormes que simplesmente não se encaixam no quadro. E, é só que a câmera foi ampliada, o que eu estava pensando?

Tentativa nº 1 - hackear dados


No início da pasta em Documentos, há um arquivo de log. O jogo escreveu uma placa de vídeo lá e informou que havia selecionado a configuração "genérico dx7". Ele diz que você instalou o Intel GMA 950, que realmente estava no computador anterior.

Mas com isso você tem o Intel Iris Graphics 6100. Algo está realmente errado. Você assume que o jogo determina os recursos gráficos do computador, comparando-o com um banco de dados de placas gráficas, em vez de verificar os recursos da própria placa. Porque é exatamente isso que os desenvolvedores AAA fazem e, se você trabalhou no RTS de código aberto , sabe como isso acontece.


O conteúdo do arquivo de log. Se você acha engraçado que a Intel seja designada como 0x8086, você escolheu o artigo apropriado.

Por acaso, você encontra notas de atualização antigas que confirmam suas preocupações. Você olha para a pasta GameData, mas há apenas .bar e .xmb arquivos que são principalmente ilegível. Você está procurando grep " Intel " e " dx7 ". Nada acontece. Exclua vários arquivos ... Nada acontece. Mas você sabe que os jogos AAA podem armazenar alguns recursos em lugares estranhos, e essa é a porta dos jogos do Windows no Mac; portanto, tudo pode ser muito bizarro aqui.

Por fim, você encontra o arquivo “render.bar”; se você o mover, o jogo trava quando é iniciado. Não é muito claro, mas não é ruim. Abrimos o arquivo no Hex Fiend , porque esse programa geralmente permite que você aprenda algo sobre arquivos binários. E assim acontece. Parte desses dados é um texto legível. Alguns deles são shaders. Mas a maioria dos dados é estranhamente dividida por espaço vazio ...


Interface Hex Fiend É minimalista, mas funciona. Os primeiros bytes são as letras "ESPN", que provavelmente são o cabeçalho .bar.

Você exclama: "Obviamente, isso é UTF-16!" O jogo foi criado para Windows, por isso é lógico que armazene texto em UTF-16, desperdiçando quase metade de um arquivo de dados de 30 megabytes. Afinal, o Windows adorava o UTF-16 (embora não devesse ), então por que não usar essa codificação e shaders ASCII em um arquivo. É uma ideia lógica!

(Na verdade, demorei um dia para descobrir isso) O

Hex Fiend tem a opção de analisar bytes como UTF-16, então, novamente, estamos procurando a string grep dx7. Existem várias entradas. Nós os substituímos pela linha dx9, que também é encontrada nos dados, salve o arquivo e inicie o jogo.

Sim Os logs novamente não exibem "dx9". Isso provavelmente está definido em outro lugar.

Vamos tentar outra coisa. Suponha que o jogo reconheça placas gráficas e seus identificadores hexadecimais sejam armazenados nos logs (no meu caso, 0x8086 é Intel e também 0x2773). Estamos procurando por “8086” no Hex Fiend, encontramos algo e parte do texto parece promissora. A Intel 9 Series é a série GMA 950 e a Age 3 provavelmente não funcionou muito bem nela.


Alguns números são como identificadores de cartão (2582, 2592); talvez se você substituí-los, algo mudará. De qualquer forma, fizemos uma cópia do arquivo, para que você possa tentar.

Infelizmente, este também é um beco sem saída. De fato, estamos estudando o arquivo errado. Felizmente, posso lhe dizer isso porque gastei muito tempo nisso e tentei demais . Essa coisa demorou cerca de 20 horas. E isso não está contando o tempo para todas as capturas de tela ... O

arquivo desejado é chamado "DataP.bar" e, se substituirmos sabiamente o ID, veremos algo completamente novo nos logs:


Não entendo por que esse "dx7 genérico" não é o mesmo da captura de tela acima.

Obviamente, no jogo ainda estamos limitados pelas configurações "Baixa", ou seja, os logs não mentem. Estávamos esperando um médium, mas infelizmente .

Parece que mais dados de hackers não podem ser alcançados. É hora de levar as ferramentas a sério.

Tentativa nº 2 - hackers


Portanto, se nada der certo, resta uma última coisa: alterar o próprio arquivo executável (isto é, como eles disseram em 2005, "quebrá-lo"). Parece que hoje é simplesmente chamado de hacking, mas esse termo foi preservado no campo da pirataria de jogos (é claro, eu pessoalmente nunca fiz isso).

Passamos para a parte mais interessante do artigo (sim, você já leu mil palavras, mas não foi curioso?).

Funil do desmontador


Nesse estágio, nossa ferramenta será um desmontador: um aplicativo que recebe o código binário do arquivo executável e o transforma em código assembler legível (ha ha). O mais popular deles é o IDA Pro, mas é incrivelmente caro e não tenho certeza se ele funciona em um Mac; portanto, usarei o Hopper . Não é gratuito (custa US $ 90), mas pode ser usado livremente por 30 minutos por vez, com quase todas as funções necessárias.

Não explicarei a interface em detalhes, porque tudo está bem explicado no tutorial Hopper , mas vou lhe dizer o que farei e tentarei tirar capturas de tela suficientes para que você possa aprender alguma coisa.

Primeiro, você precisa dizer: demorei cinco dias para resolver e muitas tentativas malsucedidas foram feitas. Basicamente, falarei sobre etapas bem-sucedidas, para que pareça mágico. Mas foi difícil . Não se preocupe se tiver algum problema.

E mais uma observação: "cracking" geralmente é ilegal e / ou executado para fins ilegais. Eu demonstro um exemplo bastante raro de uso bastante legítimo, que ao mesmo tempo é completamente "chapéu branco", então tudo está em ordem aqui. Assumirei que, estritamente falando, isso é ilegal / contrário ao contrato do usuário final, mas, obviamente, é um caso de força maior . Seja como for, pague pelo que quiser e só por isso, porque o anti-consumismo é legal.




Arraste o aplicativo Age 3 para abri-lo. Basta selecionar "x86" em vez de powerPC, porque o jogo é algo em 2005.

Etapa 1 - procure o fragmento desejado


Surpreendentemente, isso pode ser o mais difícil. Temos um código desmontado, mas não temos idéia de onde procurar. Sim, alguns jogos possuem símbolos de depuração, para que você possa ler os nomes das funções, mas não no nosso caso. Hopper nos dá nomes como "sub_a8cbb6", com os quais temos que lidar sozinhos.

Felizmente, temos uma vantagem: nosso arquivo de log . É altamente provável que literais de seqüência de caracteres sejam usados ​​ao gravar no log, ou seja, ASCII são exibidos no arquivo executável. Podemos procurá-los com grep, porque não vamos nos livrar deles por nenhuma compilação (no entanto, eles podem ser ocultados pela ofuscação do código. Felizmente, isso não aconteceu). Encontrar literais de string grep é geralmente a primeira coisa que eles fazem quando desmontam um programa. Então, vamos inserir "fornecedor encontrado" na caixa de pesquisa do funil e ele encontrará algo:


Ah, sim, literais de string, eu os adoro.

Isso por si só não nos adiantou tanto. Porém, como o endereço dessa "sequência de literais" é codificado, podemos encontrar links para ele. Hopper os destacou em verde à direita: "DADOS XREF = sub_1d5908 + 1252". Clicando duas vezes na linha, passaremos ao procedimento hero - sub_1d5908 .


Aqui encontramos o código do assembler. Acredita-se que seja difícil de ler, mas não é. O mais difícil é entender.

Por padrão, o Hopper usa "Intel sintaxe", ou seja, o primeiro operando é o receptor e o segundo é a fonte. Na linha destacada, vemos mov dword [esp+0x1DC8+var_1DC4], aXmlRenderConfi. Vamos fazer isso.

Nossa primeira linha em montador


movÉ uma equipe MOV conhecida por estar em x86 Turing-complete . Ele move (na verdade copia) os dados de A para B, o que significa essencialmente ler A e gravá-los em B. Com base no exposto, aXmlRenderConfiesse é A e dword [esp+0x1DC8+var_1DC4]B, o destinatário. Vamos dar uma olhada mais de perto nisso.

aXmlRenderConfi- esse é o valor que Hopper nos ajuda. Na verdade, esse é um apelido para o endereço de memória da string literal em que clicamos alguns segundos atrás. Se você dividir a janela e olhar o código no modo Hex (que é o modo preferido para hackers), veremos lá 88 18 83 00.


O funil destaca convenientemente os mesmos fragmentos selecionados nas duas janelas.

Se o valor "flip" estiver correto, obteremos 0x00831888 - o endereço de memória da string literal (em uma das capturas de tela mostradas acima, ela fica destacada em amarelo). Portanto, um enigma é resolvido: o código executa MOV, ou seja, grava o endereço 0x00831888.

Por que está escrito como 88 18 83 00, não como 00 83 18 88? Isso aconteceu por causa da ordem dos bytes - um tópico bastante confuso que geralmente tem pouco impacto. Essa é uma daquelas coisas que fazem as pessoas dizerem que “os computadores não funcionam”. Para nós, isso significa que esse problema ocorrerá em todos os números com os quais trabalharemos hoje.

Você deve ter notado que eu disse "anote o endereço" e isso é verdade: não nos importamos com o conteúdo, que acabou sendo uma string literal com um byte zero no final. Anotamos o endereço porque o código se referirá posteriormente a esse endereço. Este é um ponteiro.

Que tal dword [esp+0x1DC8+var_1DC4]? Tudo é mais complicado aqui. dwordsignifica que estamos trabalhando com "palavras duplas (palavras duplas)", ou seja, com 16 * 2 bits. Ou seja, copiamos 32 bits. Lembre-se: nosso endereço é0x00831888, ou seja, 8 caracteres hexadecimais e cada caractere hexadecimal pode ter 16 valores, ou seja, 4 bits de dados. Portanto, obtemos 8 * 4 = 32. A propósito, a notação "32 bits" para sistemas de computadores veio daqui. Se usássemos 64 bits, o endereço de memória seria escrito como 0x001122334455667788, seria duas vezes maior e 2 ^ 32 vezes maior, e teríamos que copiar 16 bytes. Portanto, teríamos muito mais memória, mas copiar o ponteiro exigiria o dobro de trabalho e, portanto, 64 bits não são duas vezes mais rápidos que 32 bits. A propósito, uma explicação mais detalhada do termo "palavra" pode ser encontrada aqui . Porque é claro que é mais complicado do que minha explicação.

Então, e a parte entre colchetes? Os colchetes significam que calculamos o que está dentro e o consideramos um ponteiro. Portanto, o comando MOV gravará no endereço de memória obtido dos cálculos. Vamos analisar novamente com mais detalhes (e não se preocupe, quando terminar, você saberá essencialmente como ler o código do assembler com a sintaxe da Intel).

espé um registro que no assembler é o equivalente mais próximo de uma variável. Existem muitos registros na arquitetura x86, e diferentes registros têm diferentes formas de aplicação, mas não nos importamos. Saiba mais sobre isso aqui.. Lembre-se de que existem registros especiais para números de ponto flutuante, bem como "sinalizadores" que são usados ​​para comparações e operações similares. Tudo o resto são apenas números hexadecimais que Hopper retrabalhou um pouco para nos ajudar. var_1DC4- -0x1DC4podemos vê-lo no início do procedimento ou no painel direito. Isso significa que o resultado do cálculo [esp+0x1DC8+var_1DC4]será [esp + 0x1DC8 — 0x1DC4]igual a [esp + 4]. Em essência, isso significa "pegar o valor de 32 bits do registro ESP, adicionar 4 e interpretar o resultado como um endereço de memória".

Então, repetimos: escrevemos o endereço da string literal em "esp + 4". Esta é uma informação correta, mas é completamente inútil. O que isto significa?

Essa é a dificuldade em ler o código do assembly: ao analisar o que ele deve fazer. Aqui temos uma vantagem: sabemos que ele provavelmente escreve algo no log. Portanto, podemos esperar uma chamada para uma função que grava no log. Vamos procurá-la.


Na captura de tela acima, existem várias chamadas semelhantes, bem como o comando callque chama o procedimento. Hopper geralmente fala sobre isso (não sei por que ele não fez isso aqui), mas, em essência, damos argumentos para chamar a função. Se você clicar em diferentes chamadas de sub-rotina, encontraremos algo chamado imp___jump_table___Z22StringVPrintfExWorkerAPcmmPS_PmmPKcS_. Este é o nome decodificado da função, e a parte “Printf” é suficiente para entender que ela realmente gera alguma coisa. Nós não nos importamos como ela faz isso.

Dê um passo para trás


Nesta fase, nos afastamos completamente do que fizemos: tentamos convencer o jogo de que nossa placa de vídeo de 2015 era poderosa o suficiente para lançar o jogo de 2005. Vamos resumir. Encontramos o local em que o log está sendo gravado. Se tivermos sorte, aqui está o código que verifica os parâmetros e os recursos das placas gráficas. Felizmente, tivemos muita sorte (existe uma alta probabilidade de que, de outro modo, este artigo não teria sido).

Para entender melhor, usaremos a função Gráfico de fluxo do controle de tremonha, que divide o código em pequenos blocos e desenha setas indicando diferentes transições. Isso é muito mais fácil de entender do que no assembler comum, no qual muitas vezes tudo está uma bagunça.


A chamada para a entrada do log está no meio de uma função terrivelmente longa, porque isso, novamente, geralmente acontece em jogos AAA. Aqui, devemos procurar outros literais e "dicas" da string Hopper, na esperança de encontrar algo. No início do procedimento, está o restante do registro, e abaixo há partes interessantes sobre os parâmetros "forçando dx7" e "Muito alto", além de um problema com a versão dos pixel shaders ... que temos.

O log de nossos dados "invadidos" relatou que tínhamos pixel shaders da versão 0.0 e eles são incompatíveis com os parâmetros "Alto". Este código está localizado no canto inferior esquerdo da nossa função e, se você olhar com atenção, poderá ver algo curioso (destacado por seu humilde servo): este é o mesmo código repetido quatro vezes para os parâmetros "Muito Alto", "Alto", "Médio" e "Baixo". E sim, levei várias horas para encontrar isso.


Eu destaquei em azul o bloco em que “pixel shader version 0.0 High” é exibido no log. Eu destaquei peças semelhantes com outras cores bonitas.

As únicas diferenças são que escrevemos "0x0", "0x1", "0x2" e "0x3" em lugares diferentes. É suspeito de ser semelhante ao arquivo de parâmetros que examinamos acima e ao parâmetro optiongrfxshaderquality (talvez você ainda não tenha entendido isso. Mas é, acredite).

Nesta fase, podemos tentar seguir de maneiras diferentes, o que eu fiz. Mas serei breve - faremos o mais necessário: descubra por que a verificação do pixel shader falha. Vamos procurar um ramo. O bloco de saída para o log é linear, o que significa que deve estar em algum lugar acima.


SEMVER fãs, veja um formato de versão mais avançada: números de ponto flutuante.

E, de fato, logo acima dele há jbum comando de transição (pense em "ir"). Este é um " salto condicional " e a condição é " se for menor ". Os “ifs” do assembler funcionam através de sinalizadores de registro , sobre os quais falei brevemente acima. Basta saber que precisamos examinar o comando acima: provavelmente, ele define o sinalizador. Muitas vezes, este é um comando cmpou test, mas usado aqui ucomiss. Isso é barbárie. Mas ela pesquise facilmente: sem graça . Este é um comando de comparação de ponto flutuante e explica por que o código foixmm0: Este é um registro de números de ponto flutuante. Isso é lógico: os logs relatam que nossa versão dos pixel shaders é "0.0", que é um valor de ponto flutuante. Então, o que está localizado no endereço de memória 0x89034c? Os dados hexadecimais têm uma aparência 00 00 00 40e podem ser confusos. Mas sabemos que devemos esperar números de ponto flutuante, então vamos interpretar os dados assim: isso significa 2.0. Qual é um valor lógico de ponto flutuante para a versão dos pixel shaders que a Microsoft realmente usou na sua linguagem de sombreamento de alto nívelem que os shaders do DirectX são gravados. E o Age 3 é um jogo DirectX, mesmo que a porta do Mac use o OpenGL. Também é lógico que, em 2005, os pixel shaders da versão 2.0 sejam necessários para ativar o parâmetro High, portanto, estamos nos movendo na direção certa.

Como nós consertamos isso? Esta instrução comparação realiza uma comparação com o valor ao xmm0qual é dado na linha acima: movss xmm0, dword [eax+0xA2A8]. O MOVSS é um comando especial "move" para registradores de ponto flutuante e escrevemos 32 bits a partir do endereço, que é o resultado da avaliação de eax + 0xA2A8.

Presumivelmente, esse valor não é 2,0, mas sim 0,0. Vamos consertar.

Fazendo hackers reais


Finalmente, estamos prontos para chegar aos parâmetros do jogo High. Queremos seguir o caminho "vermelho" e pular toda a lógica com a "versão errada dos pixel shaders". Isso pode ser feito passando com êxito na comparação, ou seja, revertendo a condição de transição, mas temos mais um truque: o código é composto de tal maneira que, ao excluir a transição, seguiremos o caminho "vermelho". Vamos substituir a transição por nopuma operação que não faz nada (é impossível simplesmente excluir ou adicionar dados ao arquivo executável porque ocorrerá um deslocamento e tudo quebrará).

Eu recomendo sair do CFG para isso, porque, caso contrário, Hopper começa a enlouquecer. Se tudo for feito corretamente, na janela Hex, veremos linhas vermelhas.



Vermelho mostra as alterações que fizemos. Nesse ponto, se você pagou pelo Hopper, pode simplesmente salvar o arquivo. Mas como não paguei, vou abrir o arquivo executável no Hex Fiend, encontrar a área desejada (copiando a área do Hopper para a guia Localizar dentro do Hex Fiend) e alterá-lo manualmente. Tenha cuidado aqui, você pode quebrar tudo, por isso recomendo copiar o arquivo executável de origem em algum lugar.

Feito isso, inicie o jogo, vá para as opções ... E um brinde - podemos selecionar "Alto". Mas não podemos escolher "médio". E não podemos usar os altos parâmetros das sombras. No entanto, estamos progredindo!


Basta dar uma olhada nessas reflexões magníficas na água!

Aprimoramentos


Na verdade, você pode corrigir isso da melhor maneira. Nossa solução funcionou, mas podemos fazê-lo para que a condição seja atendida corretamente: fazendo o jogo pensar que nossa placa de vídeo realmente possui shaders da versão 2.0.

Como lembramos, o jogo carrega o valor no endereço eax+0xA2A8. Você pode usar o Hopper para descobrir o que foi originalmente gravado lá. Precisamos encontrar um comando no qual 0xA2A8 seja usado como operando destinatário (escolhi 0xA2A8 porque é um valor bastante específico e não podemos ter certeza de que o mesmo registro não seja usado em outro lugar). Aqui, a luz de fundo da tremonha é muito útil para nós, porque podemos clicar em 0xA2A8 e procurar as peças amarelas no CFG.


Nossa vitória é um pouco maior: como esperado, escrevemos o valor de algum tipo de registro de ponto flutuante. Admito que realmente não entendo o que está acontecendo antes disso (meu melhor palpite: o jogo verifica a possibilidade de compressão de textura), mas isso não é particularmente importante. Vamos escrever "2.0" e continuar o trabalho.

Para fazer isso, usaremos a "Instruções de montagem" do aplicativo Hopper e, em seguida, faremos as mesmas manipulações no Hex Fiend.




Usamos MOV em vez de MOVSS porque escrevemos o valor usual, e não o valor especial do registro de ponto flutuante. Além disso, está em ordem de bytes direta, ou seja, invertida.

Você notará que algo estranho está acontecendo: nós "comemos" a equipe jb. Isso aconteceu porque o novo comando usa mais bytes que o anterior e, como eu disse, não podemos adicionar ou remover dados do arquivo executável para salvar compensações. Portanto, Hopper não tem escolha a não ser destruir o time jb. Isso pode se tornar um problema, e podemos eliminá-lo movendo o comando para cima (afinal, não precisamos mais escrever o valor xmm3). Isso funcionará aqui, porque, de qualquer forma, estamos fazendo a ramificação no caminho certo. Por sorte.

Se você iniciar o jogo ... nada mudará muito. Sugeri que a série Intel 9 não é rápida o suficiente, então o jogo reclama. Isso não é um problema, porque, na realidade, estamos lutando por "Muito Alto".

Qual foi o cartão mais poderoso em 2004? NVIDIA GeforceFX 6800. Vamos nos convencer a convencer o jogo de que ele está instalado.

No buraco do coelho


Sim, esta é uma seção completamente nova.

Sabemos que o jogo usa códigos Hex para se referir a placas gráficas e que recebe informações de arquivos de dados. Sabemos que isso deve acontecer na função que estamos estudando (pelo menos seria estranho se não estivesse). Para que possamos encontrar o código que faz isso. Mas ainda pode ser difícil, porque não sabemos exatamente o que procurar.

Temos uma pista: as opções se comportam de maneira estranha; há Baixa / Alta, mas não Média. Então, talvez exista outro código que lide com todos esses "Muito Alto", "Alto", "Médio" e "Baixo". E ele realmente está, na mesma função. Ele está muito mais cedo na CFG e parece interessante.


Podemos supor que os dados brutos sejam armazenados em algum tipo de XML, porque é muito provável, porque existem vários outros arquivos de dados que são XML. E XML é essencialmente um monte de ponteiros e atributos. Portanto, o código aqui provavelmente verifica alguns atributos XML (e, de fato, abaixo é "esperado um dos ID, Muito Alto ...", o que é muito semelhante à verificação da correção do arquivo). Podemos imaginar uma estrutura XML semelhante na qual os valores booleanos determinam a qualidade dos shaders:


Vale a pena considerar que esse é um palpite muito arbitrário. Tudo pode parecer um pouco diferente.

Uma parte muito interessante é mostrada no lado esquerdo do CFG (para você, pode estar à direita, mas à esquerda na captura de tela): vemos a mesma var_CCque é usada no código que grava o ID do dispositivo no log. Ou seja ebp+var_CC, provavelmente se refere ao ID do nosso dispositivo, 0x2773. O estranho é que não há literal de cadeia de caracteres na primeira "verificação", mas isso é um bug do Hopper: o endereço 0x0089da8e contém dados hexadecimais 69006400, que em UTF-16 significa "id" (na verdade, provavelmente Hopper não conseguiu entender isso porque UTF16). E estamos apenas procurando por um ID.

Você sabe o que? Vamos executar o depurador e verificar tudo na realidade.

Depuração de jogos


Abra uma janela do terminal e execute lldb (apenas digite lldb e pressione enter). Primeiro, precisamos dizer ao LLDB para seguir a idade 3 e, em seguida, podemos iniciar o jogo ligando run. O jogo começa e não temos nada útil.


Agora precisamos adicionar um ponto de interrupção: para confirmar nossas suposições, queremos interromper a execução quando chegarmos ao código mostrado acima. Não temos código-fonte, mas isso não importa: podemos definir um ponto de interrupção no endereço de memória. E nós temos o endereço no código. Vamos adicionar uma parada a uma chamada MOV que recebe um "id", seu endereço é 0x001d5e68. Para adicionar um ponto de interrupção, basta digitar br set -a 001D5E68. Depois de iniciar o jogo, ele para e o LLDB mostra o código desmontado (na sintaxe da AT&T, não na Intel, portanto todos os operandos são invertidos, mas vemos que esse é o mesmo código). Você pode perceber que o LLDB nesse caso é realmente mais inteligente que Hopper; ele nos diz que estamos executando operações relacionadas à saída XML e printf.



Sinta-se como um hacker?

Para seguir em frente, vamos dar uma "instrução de passo". Um pequeno comando para isso
é ni. Repetindo várias vezes, percebemos que estamos de volta ao ponto em que começamos. Este é um ciclo! E isso é completamente lógico: provavelmente estamos repetindo algum tipo de XML. De fato, Hopper nos mostrou isso - se seguirmos o CFG, veremos um (possível) ciclo.


Isso significa:if (*(ebp+var_CC) == *(ebp+var_28)) { *(ebp+var_1D84)=edi }

Sua condição de saída é que o valor ebp+var_1D84seja diferente de zero. E vemos um parâmetro de código interessante no destacado por mim var_CC.

Vamos adicionar um ponto de interrupção neste cmpe vamos descobrir isso.



Esquerda: defina um ponto de interrupção no CMP e continue a execução. À direita, monitoramos os registros e a memória.

Examinamos os registros com reg re o valor da memória com mem read $ebp-0x28. eaxe, na verdade, contém 0x2773, e o valor na memória agora é 0x00A0. Se você executá- thread clo várias vezes , veremos que a iteração ocorre em diferentes IDs em busca de correspondências. Em algum momento, os valores coincidirão, o loop sairá e o jogo começará. Agora sabemos como gerenciá-lo.

Lembre-se do arquivo de log: ele identificou o dispositivo e o fabricante. Provavelmente em algum lugar existe um ciclo semelhante para os nomes dos fabricantes. E, de fato, é muito semelhante e está localizado no CFG diretamente acima do nosso ciclo. Esse loop é um pouco mais simples e procuramos a string "vendor".

Desta vez, a comparação é feita comvar_D0. Essa variável é realmente usada como o ID do fabricante. Se colocarmos um ponto de interrupção aqui e verificarmos tudo, veremos o familiar 0x8086 em eaxe, ao mesmo tempo, uma comparação com 0x10DE está acontecendo. Vamos escrever eaxe ver o que acontece.



Uau.

Ou seja, 0x10DE é NVIDIA. De fato, é possível entender isso quando se estuda um arquivo de dados, mas não é muito interessante. Agora, a pergunta é: qual é o identificador da GeforceFX 6800? Podemos usar o LLDB e apenas verificar cada identificador, mas isso levará tempo (existem muitos, tentei encontrar o correto). Então, vamos dar uma olhada em render.bar neste momento.


Suponha que seja "XMB", a representação binária de XML usada no Age 3.

Este arquivo é bastante caótico. Porém, após as tags Geforce FX 6800, vemos vários identificadores. Provavelmente, precisamos de um deles. Vamos tentar 00F1.

A maneira mais fácil de testar isso é usar o LLDB e definir os pontos de interrupção desejados. Vou pular esta etapa, mas direi que 00F1 surgiu (felizmente).

Nesse estágio, precisamos responder à pergunta: "Como tornar essa mudança permanente?" Parece que será mais fácil alterar os valores var_CCe var_D0em 0X00F1 e 0X10DE. Para fazer isso, precisamos apenas de espaço para o código.

Uma solução simples seria substituir uma das chamadas de registro de log por um NOP, por exemplo, uma chamada de subsistema. Isso nos libertará várias dezenas de bytes, e isso é ainda mais do que necessário. Vamos selecionar tudo isso e substituí-lo pelo NOP. Então, precisamos escrever informações usando as variáveis ​​MOV: assembler mov dword [ebp+var_CC], 0xF1e mov dword [ebp+var_D0], 0x10DE.



Antes e depois

Vamos rodar o jogo. Finalmente, conseguimos algo: você pode escolher entre Baixo, Médio ou Alto, além de ativar os parâmetros Altos para sombras.


Mas ainda não podemos ativar o Very High, e isso é triste. O que está acontecendo?


Ah, entendi.

Como se viu, o Geforce FX 6800 deve suportar os shaders de pixel da versão 3.0 e definimos apenas a versão 2.0. Agora é fácil corrigi-lo: basta escrever o ponto flutuante 3.0 ebp+0xA2A8. Este valor é 0x40400000.

Finalmente, o jogo não é mais caprichoso e podemos desfrutar de configurações muito altas.


Estranho, mas parece completamente diferente. As cores são muito menos vibrantes e o nevoeiro dependente do alcance apareceu. Há também um pequeno problema de conflito de sombra (também está nas capturas de tela acima, devido aos parâmetros High shadow, não à qualidade dos shaders), que ainda não consegui resolver.

E este é o fim de nossa jornada, obrigado pela leitura.

Além disso, darei exemplos de como cada um dos parâmetros do jogo se parece, porque todas as capturas de tela encontradas online são terríveis ou incompletas.

Os gráficos no Medium são bastante aceitáveis, mas sofrem com a falta de sombras. Pelo que sei, o Very High geralmente adiciona uma LUT completamente diferente (uma boa explicação desse termo pode ser encontrada na seção Gradação de cores deste artigo.), neblina à distância e talvez até um modelo de iluminação completamente diferente? A imagem é muito diferente e é bastante estranha.





De cima para baixo: Muito alto, Alto (com opções de sombra alta), Médio (apenas com sombra ativada), Baixo (com sombra desativada).

Outros links úteis


O blog ao qual vinculei acima contém uma análise maravilhosa dos pipelines de renderização modernos: http://www.adriancourreges.com/blog/ O

Rate 0 AD é um jogo RTS de código aberto do mesmo gênero: https://play0ad.com . Ela precisa de desenvolvedores.

Se você quiser saber mais sobre o formato XMB, poderá ler seu código no 0 AD . Não verifiquei, mas tenho certeza de que esse é o mesmo formato, porque a pessoa que escreveu o decodificador XMB para o Age 3 no céu do Age of Empires também implementou esses arquivos no código-fonte 0 AD em 2004. Portanto, essa será uma lição divertida de paleontologia de código. Obrigado por todo o trabalho, Ykkrosh.

Lembro exatamente que li um excelente tutorial sobre o uso do Hopper, mas agora não consigo encontrar o link. Se alguém souber do que estou falando, solte o link.

No final, gostaria de mencionar o projeto Cosmic Frontier: um remake da clássica série Escape Velocity (mais de Override, mas também é compatível com Nova). Este é um jogo incrível, e é muito triste que poucas pessoas o conheçam. Agora, os criadores lançaram uma campanha no Kickstarter, e o desenvolvedor principal possui um blog de desenvolvimento muito interessante , que fala sobre o uso do Hex Fiend para fazer a engenharia reversa dos formatos de dados do jogo original. Esta é uma ótima leitura. Se você acha que meu artigo foi insano. então em uma das postagens eles:

  • Lançamos o emulador clássico de Mac para usar as APIs obsoletas há muito tempo para ler um arquivo de dados criptografados.
  • Criptografia de engenharia reversa a partir do código do jogo
  • Eles usaram um hack do sistema de arquivos para acessar os mesmos dados em um Mac moderno.

Sim, são pessoas verdadeiramente apaixonadas, e espero que o projeto seja concluído com sucesso, porque EV Nova é o meu jogo favorito.

All Articles